电机专题(直流有刷、无刷电机,步进电机……)超详细!!!

电机专题

1. 初识电机

1.1 电机简介

电机,即电动机(motor)俗称马达,是电能转化为机械能的总称

1.2 电机分类

  • 按电源分可以分为:直流电机和交流电机(异步和同步电机)

  • 按用途用分可分为:驱动类电机,和控制类电机

1.3 各类电机简介

1.3.1 直流有刷电机

  • 直流有刷电机包括直流有刷电机和直流有刷减速电机。

  • 直流有刷电机是内含电刷装置的将直流电能转换成机械能的(直流电动机)。

  • 直流有刷电机是所有电机的基础,它具有启动快、制动及时、可在大范围内平滑地调速、控制电路相对简单等特点。

有刷电机内部示意图:

在这里插入图片描述

直流有刷电机的特点:

  • 成本低,操控简单

  • 电刷和换向器之间有摩擦,导致换向时产生电火花易产生电磁干扰,故障多,维护工作量大,寿命短(600~1000小时),电机故障通常只要更换碳刷;

生活中常见的直流有刷电机:

在这里插入图片描述

直流有刷减速电机

  • 直流减速电机,即齿轮减速电机,是在普通直流电机的基础上,加上配套齿轮减速箱提供较低的转速,较大的力矩。同时,齿轮箱不同的减速比可以提供不同的转速和力矩。这大大提高了,直流电机在自动化行业中的使用率。

在这里插入图片描述

直流有刷电机工作原理:

  • 左手定则:左手定则是判断通电导体处于磁场中时,所受安培力 F (或运动)的方向、 磁感应强度B的方向以及通电导体棒的电流 I 三者方向之间的关系的定律。 通过左手定则可以知道通电导体在磁场中的受力方向,示意图如下:

在这里插入图片描述

  • 有刷电机的工作原理是通过机械换向,将电源通过电刷和换向器将电流引入电枢绕组,实现电枢电流的换向
  • 下图是直流有刷工作原理图:

在这里插入图片描述

AB为碳刷,DC为换向器,根据左手定则,持续顺时针旋转,将电源正负极调过来则持续逆时针旋转。

1.3.2 直流无刷电机

  • 直流无刷电机(BLDC)是指无电刷和换向器(或集电环)的电机,又称无换向器电机。

  • 无刷电机又分为:内转子式无刷电机(一般工业上用的比较多),外转子式无刷电机(一般用在飞行器上),永磁同步电机(PMSM)

在这里插入图片描述

  • 永磁同步电机(PMSM)电机与一般的直流无刷电机(BLCD)电机有所不同,主要表现在反电动势上,一般的无刷电机方波(梯形波),而PMSM是正弦波,示意图如下:

在这里插入图片描述

无刷电机工作原理:

  • 无刷电机的工作原理是通过电子控制器控制无刷电机的电子换向器,使电流按照一定的规律在无刷电机的三相线圈之间流动,产生旋转磁场。无刷电机内部有永磁体,这个旋转磁场会与永磁体的磁场相互作用,产生转矩,从而使电机转动

直流无刷电机的特点:

  • 没有了碳刷结构,干扰小,噪音低,运转流畅,高速,而且寿命更长(10000小时以上,损坏只能更换);

  • 控制较为复杂,可以使用方波(6步换向)或者正弦波换相(FOC)。

1.3.3 舵机

舵机(Servo)实际上可以看作一个伺服电机,主要由直流电机、减速齿轮组、角度传感器、控制电路构成。

在这里插入图片描述

舵机的特点:

  • 一般而言旋转角度范围在0-180°

  • 闭环系统,可以反馈转动的角度信息

  • 通过控制PWM脉冲占空比大小,指定输出轴的旋转角度

  • 一般是用在飞机的舵面,机器人关节等等

1.3.4 伺服电机

  • 伺服电机(servo motor)可以理解为绝对服从控制信号指挥的电机。其主要特点是,当信号电压为零时无自转现象(不会由于惯性而继续转)。它由两部分组成:伺服驱动器、电机(通常是永磁同步电机,PMSM)。

在这里插入图片描述

伺服电机特点:

  • 可使控制速度,位置精度非常准确,效率高,寿命长。

  • 驱动器可设置电机工作在转速、转矩、位置等模式。

  • 价格昂贵,一套至少都是大几k以上;控制较复杂,一般需要专业的人士才能控制。

  • 一般是用在自动化生产线,机器人,自动化工业设备等等

1.3.5 步进电机

  • 步进电机是一种把电脉冲信号转换为角位移或线位移的电动机。在空载低频的情况下,一个脉冲就是一步,可以精准的控制旋转角度。但步进电机是开环的控制电机,如有干扰(不能满足空载低频),是否一个脉冲对应一步是不一定的。步进电机常根据外观尺寸来得名,如42步进电机、57步进电机等。

在这里插入图片描述

步进电机工作原理:

步进电机的工作原理是 利用电子电路将直流电变成分时供电的多相时序控制电流,用这种电流为步进电机供电,步进电机才能正常工作。步进电机驱动器是给步进电机提供电源和控制其运转的设备,它通过接收脉冲信号来驱动步进电机按照设定的方向旋转

步进电机特点:

  • 控制简单,低速扭矩大、速度取决脉冲频率、角位移取决脉冲个数等特点;
  • 存在空载启动频率,不可高于该频率否则就会丢步甚至是堵转。
  • 一般是应用在3D打印机,绘图仪,数控机床等等

2. 驱动直流有刷电机

2.1 H桥驱动电路分析

  • H桥驱动电路是一种典型的直流电机控制电路,它能够通过控制开关的通断来控制电机的正反转和转速。

  • 下图是H桥电路最简单的正反转示意图:

    在这里插入图片描述

    在电路中可以做电子开关的有三极管和MOS管。可以使用这两种器件代替开关从而实现电路可控的效果

    在这里插入图片描述

  • 电路组成

H桥驱动电路主要由4个开关(Q1、Q2、Q3、Q4)、电机(M)和电源(V)组成。其中,Q1和Q2为一对开关,Q3和Q4为另一对开关,电机位于两对开关的中间,电源为直流电源。

  • 工作原理

H桥驱动电路的工作原理是通过控制开关的通断来控制电机的正反转和转速。当Q1和Q4导通时,电流从电源正极经过Q1和Q4流向电机的两个端子,电机开始正向旋转;当Q2和Q3导通时,电流从电源正极经过Q2和Q3流向电机的另外两个端子,电机开始反向旋转。通过改变开关的通断状态和持续时间,可以控制电机的转速和方向。当开关Q1和Q3闭合、Q2和Q4断开或者当开关Q2和Q4闭合、Q1和Q3断开时直流电机不旋转。此时可以认为电机处于“刹车”状态,电机惯性转动产生的电势将被短路,形成阻碍运动的反电势,形成“刹车”作用。

  • 分析H桥电路

    下面开始以MOS管搭建的H桥电路解释电机正反转控制。

    要使电机运转,必须使对角线上的一对MOS管导通。如图,当Q1管和04管导通时(此时必须保证G2和G3关断),电流就从电源正极经Q1从左至右穿过电机,负极。按图中电流箭头所示,该流向的电流将驱动电机顺时针转动。然后再经Q4回到电源;

    另一对MOS管Q2和Q3导通的时候(此时必须保证Q1和Q4关断) ,电流从右至左流过电机从而驱动电机沿逆时针方向转动。

在这里插入图片描述

驱动电机时,保证H桥两个同侧的MOS管不会同时导通非常重要,如果MOS管Q1和Q2同时导通,那么电流就会从电源正极穿过两个MOS管直接回到负极,此时电路中除了MOS管外没有其它任何负载,因此电路上的电流就达到最大值,烧坏MOS管和电源。03和Q4同时导通是同样的道理;

  • 注意事项

在H桥驱动电路中,需要特别注意以下几点:

(1)在电机驱动中,要保证H桥两个同侧的MOS管不会同时导通,否则电流会从电源正极直接穿过两个MOS管回到负极,导致电路中的电流过大而烧坏MOS管和电源。

(2)简单的开关只能控制电机的正反转,引入PWM控制可以实现方向和速度调节。PWM控制通过控制开关的通断时间来控制电机的平均电压,从而控制电机的转速和方向。

(3)H桥驱动电路的频率不能太低或太高。频率太低会导致电机转速过低,噪声较大;频率太高会导致MOS管的开关损耗增加,降低系统的效率。

2.2 引入PWM脉冲调制

  • 简单的开关只能控制电机正反转,引入PWM控制可以实现方向和速度调节调节占空比实现控速,占空比越大平均电压(电流) 越大,速度越快。

  • PWM频率一般在10KHz~20KHz之间。频率太低会导致电机转速过低,噪声较大。频率太信会因为MOS管的开关损耗而降低系统的效率。

  • 根据不同桥臂的PWM控制方式不同,大致上可以分为三种控制模式:受限单极模式单极模式双极模式。

(1)受限单极模式

  • 受限单极模式:电机电枢驱动电压极性是单一的;

  • 优点:控制电路简单。

  • 缺点:不能刹车,不能能耗制动,在负载超过设定速度时不能提供反向力矩。调速静害差大,调速性能很差,稳定性也不好。

  • 下图是受限单极示意图:

    在这里插入图片描述

  • 受限单极模式不能同时导通上半桥或下半桥,因为这种模式是单极调制,电机的电枢驱动电压极性是单一的。同时上桥臂或下桥臂导通会导致电流从电源正极直接穿过两个开关流向负极,造成电路短路,损坏开关和电源。

(2)单极模式

  • 单极模式:电机电枢驱动电压极性是单一的
  • 优点:启动快,能加速,刹车,能耗制动,能量反馈,调速性能不如双极模式好,但是相差不多,电机特性也比较好。在负载超速时也能提供反向力矩,和双极模式比各有优差,应用领域不同,需要根据不同的环境选择不同的控制模式。
  • 缺点:刹车时,不能减速到0,速度接近0速度时没有制动力。不能突然倒转。动态性能不好。调速静差稍大。

在这里插入图片描述

注:PWM和PWMN是互补通道,一般在高级定时器的通道和互补通道控制。

  • 在PWM为高电平时:MOS管1和4都导通MOS管2和3都截止,电流从电源正极,经过MOS管1,从左到右流过电机;、然后经过MOS管4流入电源负极。

在这里插入图片描述

  • 在PWM为低电平时: MOS管2和4都导通MOS管1和3都截止,根据楞次定律,存在自感电动势,电流还是从左到右流过电机,经过MOS管4和MOS管2形成电流回路。

  • 下图是回流电路示意图:

在这里插入图片描述

(3)双极模式

  • 双极模式:电枢电压极性是正负交替的
  • PWM1和PWM1N、PWM2和PWM2N是PWM互补通道。使用高级控制定时器通道和互补通道控制双极模式中,PWM1和PWM2周期相同,占空比相同,极性相反,使得对角线上的两个MOS警同时导通,同时关断。
  • 记PWM1和PWM2周期为T,PWM1高电平时间为t1,那么:

在这里插入图片描述

整理得: Umotor=(2a - 1)UDc其中: a为占空比。结论:

  • a>50%, Dpwm1>Dpwm2’电机正转;
  • -a<50%,Dpwm1<Dpwm2’电机反转;
  • a-50%,Dpwm1=Dpwm2’电机停止;
  • a-0,电机反转,速度最高;
  • a-100%,电机正转,速度最高。

下图是双极模式电路:

在这里插入图片描述

  • 在PWM1为高电平时: MOS管1和4都导通MOS管2和3都截止,电流从电源正极,经过MOS管1,从左到右流过电机,然后经过MOS管4流入电源负极。

在这里插入图片描述

  • 在PWM为低电平时: MOS管2和3都导通MOS管1和4都截止,虽然电机加了反向电压,但由于电机的负载电流较大,电流的方向仍然不改变,只不过电流幅值的下降速率叫单极模式要快,因此,电流波动较大。

在这里插入图片描述

2.3 代码部分

  • 定时器部分
 htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1;						//预分频值
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;  //向上计数模式
  htim1.Init.Period = 8400 - 1;					//重装载值
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1; //PWM工作模式1
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

定时器部分我们主要设置第2,3,4,27行,目的是为了设置我们的定时器频率,这里我们定时器频率是10khz

通用控制定时器时钟源TIMxCLK = HCLK/2=84MHz (这里我们用的是高级定时器TIM1所以是168Mhz)
设定定时器频率为=TIMxCLK/((8400-1)+1) = 10khz

  • 电机控制部分motor.c
/**
  * 函数功能: 设置电机转速
  * 输入参数: duty:占空比,取值范围是0~1000
  * 返 回 值: 无
  * 说    明: 设置比较值
  */
void TB6642_setSpeed(uint16_t duty)
{
	if( duty > 1000 )
     return;
  uint16_t ccr = duty * (8400 + 1) / 1000;// 占空比转换成比较值
  __HAL_TIM_SET_COMPARE( &htim1, TIM_CHANNEL_1, ccr );  //切换占空比函数
}
/**
  * 函数功能: 设置电机旋转方向
  * 输入参数: dir:电机旋转方向,取值是  MOTOR_DIR_CCW ,MOTOR_DIR_CW
  * 返 回 值: 无
  * 说    明: 控制IN1和IN2来决定电机方向
  *           旋转方向不仅与程序有关,也与电机接线有关,需要具体分析
  *           简单的方法是:如果控制方向与要求相反,调换两根控制线
  */
void TB6643_setMotorDir(MotorDir_Typedef dir)
{
  if( dir == MOTOR_DIR_CCW ) //电机逆时针旋转
  {
    HAL_GPIO_WritePin( GPIOA, IN1_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin( GPIOA, IN2_Pin, GPIO_PIN_RESET);
  }
  if( dir == MOTOR_DIR_CW )  //电机顺时针旋转
  {
    HAL_GPIO_WritePin( GPIOA, IN1_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin( GPIOA, IN2_Pin, GPIO_PIN_SET);
  }
}

/**
  * 函数功能: 设置电机旋转方向
  * 输入参数: ena:脉冲输出状态,取值是  MOTOR_ENABLe ,MOTOR_DISABLE
  * 返 回 值: 无
  * 说    明: 控制定时器输出脉冲
  */
void TB6643_setMotorEna(MotorSta_Typedef ena)
{
  if( ena == MOTOR_ENABLE )
  {
    HAL_TIM_PWM_Start( &htim1, TIM_CHANNEL_1);   //打开占空比输出
  }
  
  if( ena == MOTOR_DISABLE )
  {
    HAL_TIM_PWM_Stop( &htim1, TIM_CHANNEL_1);	//关闭占空比输出
  }
}

/**
  * 函数功能: 刹车制动
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: IN1=IN2=1,就是刹车制动
  */
void TB6642_breakTostop()
{
  HAL_GPIO_WritePin( GPIOA, IN1_Pin, GPIO_PIN_SET);
  HAL_GPIO_WritePin( GPIOA, IN2_Pin, GPIO_PIN_SET);
}

/**
  * 函数功能: 按键控制
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 按下返回KEY_ON
  */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}

设置电机转速函数:void TB6642_setSpeed();

设置电机旋转方向函数:void TB6643_setMotorDir();

设置电机使能函数:void TB6643_setMotorEna();

设置电机刹车函数:void TB6642_breakTostop();

设置按键控制函数:uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

  • 电机控制部分motor.h

    //按键状态
    #define KEY_ON	1
    #define KEY_OFF	0
    
    //电机方向
    typedef enum {
      MOTOR_DIR_CCW = 0,
      MOTOR_DIR_CW,
    }MotorDir_Typedef;
    
    //电机状态
    typedef enum {
      MOTOR_ENABLE = ENABLE,
      MOTOR_DISABLE = DISABLE,
    }MotorSta_Typedef;
    
    

    设置电机的按键状态,定义电机方向枚举,电机状态枚举

  • 主函数部分main.c

 while (1)
  {
	   //启动电机设置占空比10%
	if(Key_Scan(KEY1_GPIO_Port,KEY1_Pin) == KEY_ON)
	{
	  TB6642_setSpeed(1000/10); // 10%占空比
	  TB6643_setMotorDir(MOTOR_DIR_CCW); //电机方向
      TB6643_setMotorEna(MOTOR_ENABLE);  //电机状态
	}
	
	//增大占空比
	if( Key_Scan(KEY2_GPIO_Port,KEY2_Pin) == KEY_ON)
	{
		 Speed += 100;  //加100占空比
      
      if(Speed > 1000)
        Speed = 1000;
      
	  //设置电机速度
      TB6642_setSpeed(Speed);
	}
	
	//减小占空比
	if( Key_Scan(KEY3_GPIO_Port,KEY3_Pin) == KEY_ON)
	{
		 if(Speed < 1000/10)
        Speed = 0;
      else
        Speed -= 100; //减100占空比
	  
      //设置电机速度
      TB6642_setSpeed(Speed);
	}
	
	//控制电机方向
	if( Key_Scan(KEY4_GPIO_Port,KEY4_Pin) == KEY_ON)
	{
	  //i是奇数是反转运行,i是偶数是正转运行
      TB6643_setMotorDir( (++i % 2) ? MOTOR_DIR_CCW : MOTOR_DIR_CW);
	}
	
	//停止电机
	if( Key_Scan(KEY5_GPIO_Port,KEY5_Pin) == KEY_ON)
	{
		TB6642_breakTostop();  //电机刹车
	}
  }

3. 编码器

3.1 编码器简介

编码器,英文名称“encoder”,它是一种能把距离(直线位移)和角度(角位移)数据转换成电信号(脉冲信号),二进制编码并输出的传感器。编码器通常用于工业的运动控制中,用于测量并反馈被测物体的位置和状态(角度,速度),如机床、机器人、电机反馈系统以及测量和控制设备等。

在这里插入图片描述

3.2 编码器的分类

编码器可以按照监测原理编码类型来进行分类

  • 检测原理又分为光电式磁电式编码器

  • 编码类型又分为绝对式增量式编码器

  • 光电编码器

    光电编码器,是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。这是目前应用最多的传感器,光电编码器是由光源、光码盘和光敏元件组成。

    光栅盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,光栅盘与电动机同速旋转,经发光二极管等电子元件组成的检测装置检测输出若干脉冲信号,通过计算每秒光电编码器输出脉冲的个数就能反映当前电动机的转速。

    并且为判断旋转方向,码盘还可提供相位相差90°的两路脉冲信号。

    在这里插入图片描述

  • 霍尔编码器

霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲数字量的传感器。

霍尔编码器是由霍尔码盘(磁环)和霍尔元件组成。

霍尔码盘是在一定直径的圆板上等分地布置有不同的磁极。霍尔码盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。

在这里插入图片描述

  • 增量式编码器(位移和速度)

增量式编码器是将设备运动时的位移信息变成连续的脉冲信号,脉冲个数表示位移量的大小。其特点如下:

  • 只有当设备运动时才会输出信号。

    • 一般会输出通道A和通道B 两组信号,并且有90° 的相位差(1/4个周期),同时采集这两组信号就可以计算设备的运动速度和方向。 如下图,通道A和通道B的信号的周期相同,且相位相差1/4个周期,结合两相的信号值:

      • 当B相和A相先是都读到高电平(1 1),再B读到高电平,A读到低电平(1 0),则为顺时针

      • 当B相和A相先是都读到低电平(0 0),再B读到高电平,A读到低电平(1 0),则为逆时针

    • 除通道A、通道B 以外,还会设置一个额外的通道Z 信号,表示编码器特定的参考位置 如下图,传感器转一圈后Z 轴信号才会输出一个脉冲,在Z轴输出时,可以通过将AB通道的计数清零,实现对码盘绝对位置的计算。

    • 增量式编码器只输出设备的位置变化和运动方向,不会输出设备的绝对位置。

    在这里插入图片描述

  • 绝对式编码器(位置和角度)

绝对式编码器在总体结构上与增量式比较类似,都是由码盘、检测装置和放大整形电路构成,但是具体的码盘结构和输出信号含义不同。

它是将设备运动时的位移信息通过二进制编码的方式(特殊的码盘)变成数字量直接输出。其特点如下:

  • 其码盘利用若干透光和不透光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的。

    • 绝对式编码器的码盘上有很多圈线槽,被称为码道,每一条(圈)码道内部线槽数量和长度都不同。它们共同组成一套二进制编码,一条(圈)码道对应二进制数的其中一个位(通常是码盘最外侧的码道表示最低位,最内侧的码道表示最高位)。

    • 码道的数量决定了二进制编码的位数,一个绝对式编码器有N 条码道,则它输出二进制数的总个数是2的N次方个。

    • 读取这些二进制码就能知道设备的绝对位置,所以称之为绝对式编码器。

    • 编码方式一般采用自然二进制、格雷码或者BCD 码等。

      • 自然二进制的码盘易于理解,但当码盘的制造工艺有误差时,在两组信号的临界区域,所有码道的值可能不会同时变化,或因为所有传感器检测存在微小的时间差,导致读到错误的值。比如从000跨越到111,理论上应该读到111,但如果从内到外的3条码道没有完全对齐,可能会读到如001或其它异常值。

      • 格雷码(相邻的两个2进制数只有1个位不同)码盘可以避免二进制码盘的数据读取异常,因为格雷码码盘的相邻两个信号组只会有1位的变化,就算制造工艺有误差导致信号读取有偏差,最多也只会产生1个偏差(相邻信号的偏差)。

        在这里插入图片描述

3.3 编码器参数

(1)分辨率

指编码器能够分辨的最小单位。

  • 对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转(PPR)。 码盘上透光线槽的数目其实就等于分辨率,也叫多少线,较为常见的有5-6000 线。
  • 对于绝对式编码器,内部码盘所用的位数就是它的分辨率,单位是位(bit),具体还分单圈分辨率和多圈分辨率。

(2)精度

我们要知道精度与分辨率是两个不同的概念。

  • 精度是指编码器每个读数与转轴实际位置间的最大误差,通常用角度角分角秒来表示。

  • 就像有些绝对式编码器参数表里会写±20′′,这个就表示编码器输出的读数与转轴实际位置之间存在正负20 角秒的误差。

  • 精度由码盘刻线加工精度、转轴同心度、材料的温度特性、电路的响应时间等各方面因素共同决定。

(3)最大响应频率

  • 指编码器每秒输出的脉冲数,单位是Hz,也称PPS。计算公式为:
最大响应频率= 分辨率* 轴转速/60

假设某电机的编码器的分辨率为100(即光电码盘一圈有100条栅格),轴转速为120转每分钟(即每秒转2圈),则响应频率为100*120/60=200Hz,即该转速下,编码器每秒输出200个脉冲(电机带动编码器转了2圈嘛)。

(4)最大转速

  • 指编码器机械系统所能承受的最大转速

4. 驱动直流无刷电机

4.1 三步换向原理分析

  • 无刷电机内部结构

    在这里插入图片描述

    无刷电机有两股线,一股是电机的UVW三相控制线,另外一股是霍尔信号的输出线,总共五根其中三根是HV,HU,HW霍尔输出,另外两根电源线GND,VCC

  • 无刷电机驱动原理

在这里插入图片描述

此图是无刷电机的内部示意图,我们可以看出内圈它有四对磁极,外圈有很多的绕组,为了观察更加清晰,将它简化后得出右侧图片,这是我们就能明显观察出它的磁极和绕粗。

在这里插入图片描述

为了更好分析原理有进行了进一步简化,将磁极转化成一对磁极,将绕组我们也简化了一下,得到了一个只有ABC三相和一个公共点的星型示意图,然后我们再将转子放上就得到了最后我们想要的样子。

我们来简单分析一下我们得到简化后的图,可以看到U的电流是向下的,W是向左的,根据平行四边形法则得出当前磁场的矢量方向是向下的,转子和我们的矢量方向是平行的所以我们的转子也是同样的方向。

我们又知道BLCD的绕组是两两导通,也就是说同一时刻有两相是导通的,另一项是开漏的,因此我们既可以推导出六种不同的导通方式,我们把这种称作六步换向。如下图所示:

在这里插入图片描述

1.给U接24V、V悬空、W接GND,此时电机的转轴对应上图的(1)的转子位置。

2.在上一步的基础上修改接线方式,给U接24V、V接GND、W悬空,此时电机的转轴对应上图的(2)的转子位置,相较于(1) 旋转了一个角度。
3.在2.的基础上继续修改接线方式,U悬空,V接GND,W接24V,此时对应(3)。
4.在3.的基础上继续修改接线方式,U接GND,V悬空,W接24V,此时对应(4)。
5.在4.的基础上继续修改接线方式,U接GND,V接24V,W悬空,此时对应(5)。
6.在5.的基础上继续修改接线方式,U悬空,V接24V,W接GND,此时对应(6)。

在这里插入图片描述

通过上图,我们可以发现有时候UVW三相有时候是正的有时候是负的,也可以发现导通的可能是六步中的任意一个,由此我们可以提出两个问题,增加我们的理解。

1.如何实现三相极性的切换?

2.如何确定当前转子的位置?

下面我们在为上边两个问题逐一解决。

  • 1.如何实现三相极性的切换?

使用三相逆变电路来实现极性的更换,通过三个半桥,控制MOS管来进行开关动作,从而达到实现三相极性的切换的需求:

在这里插入图片描述

注意:

1.不要同侧MOS管同时导通,否则会造成短路,毁坏

2.上面的方式是直接把电源加载到线圈上,会使电机很快飙到很高的速度。

为了解决电流突然飙到很高所以一般都是将高低电平用PWM来代替,这样可以方便的控制线圈电流,从而控制转子扭矩及转速。

下图是无刷电机驱动示意图:

在这里插入图片描述

  • PWM控制直流无刷电机的常见方法

在这里插入图片描述

PWM控制分为全桥控制和半桥控制,半桥控制又分为对称式(图一图二)和不对称型(图三图四),这几种控制方法没有好与不好,看自己的喜好,但是第五种使用的是全桥模式,都是用PWM控制,由此MOS的开关开关会对MOS管造成损害,所以一般我们不使用第五种,我们一般常用的是第四种H_PWM – L_ON:上桥臂使用PWM调制,下桥臂使用高低电平直接控制,所以电机转速,取决于上桥臂PWM的占空比。

  • 2.如何确定当前转子的位置在哪?

位置检测:通过霍尔传感器读取

在这里插入图片描述

霍尔效应:正向磁场通过,输出1;反向磁场通过,输出0
三个霍尔传感器位置相差120°

霍尔传感器是根据霍尔效应制作的一种磁场传感器,它可以有效的反映通过霍尔原件的磁密度。

在这里插入图片描述

注意:我们通过一个传感器是检测不出来磁密度的,必须通过多个,我们这里是三个霍尔传感器。

下图是电角度120°的排列方式:

在这里插入图片描述

前面我们看的一个内部示意图,他的电角度看起来是60°,但是实际上是机械角度,并且是两对极的。所以它的电角度也是120°。

电角度机械角度转换公式:

电角度 = 机械角度 * 极对数

在这里插入图片描述

注意:

(1)机械角度是指电机转子的旋转角度,由m表示;
(2)电角度是指磁场的旋转角度,由e表示。
(3)当转子为一对极时,m = e;
(4)当转子为n对极时,n*m=e

下图是电角度120°的波形图:

在这里插入图片描述

由上图可以看出他们之间是存在120°的一个相位差的,上图每个格是60°

从上图还可以看到每个波形下都有像001 、101这样个数,总共有霍尔1、2、3,每当N极对对应其中一个,N极对应的那一项就是1,其他两相就是0,我们有六步,所以就有六种形态,也就是我们的六步换向,由此我们就可以通过霍尔1、2、3的排列顺序知道当前的转子的位置。

  • 下面是无刷电机真值表,每个电机厂家给的真值表是不一样的:

霍尔与绕组得电情况的真值表:

在这里插入图片描述

  • 霍尔传感器安装位置对应的波形

在这里插入图片描述

在实际运用过程中我们不知到60°的霍尔安装位置怎么操作时,我们只需把霍尔2反转,再把霍尔2,霍尔3调换位置就和120°安装位置的方法一样,就可以用相同的方法操作啦

4.2 代码部分

  • 定时器部分

    霍尔定时器初始化

    void MX_TIM5_Init(void)
    {
    
      /* USER CODE BEGIN TIM5_Init 0 */
    
      /* USER CODE END TIM5_Init 0 */
    
      TIM_HallSensor_InitTypeDef sConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
    
      /* USER CODE BEGIN TIM5_Init 1 */
    
      /* USER CODE END TIM5_Init 1 */
      htim5.Instance = TIM5;
      htim5.Init.Prescaler = 128 - 1;//预分频值
      htim5.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
      htim5.Init.Period = 0xFFFF - 1;//重转载值
      htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
      sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
      sConfig.IC1Filter = 0;
      sConfig.Commutation_Delay = 0;
      if (HAL_TIMEx_HallSensor_Init(&htim5, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM5_Init 2 */
    
      /* USER CODE END TIM5_Init 2 */
    
    }
    

    pwm定时器初始化

    void MX_TIM8_Init(void)
    {
    
      /* USER CODE BEGIN TIM8_Init 0 */
    
      /* USER CODE END TIM8_Init 0 */
    
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
      TIM_OC_InitTypeDef sConfigOC = {0};
      TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
    
      /* USER CODE BEGIN TIM8_Init 1 */
    
      /* USER CODE END TIM8_Init 1 */
      htim8.Instance = TIM8;
      htim8.Init.Prescaler = 2-1;//预分频值
      htim8.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
      htim8.Init.Period = 5600 - 1; //重转载值
      htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim8.Init.RepetitionCounter = 0;
      htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      if (HAL_TIM_Base_Init(&htim8) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
      if (HAL_TIM_ConfigClockSource(&htim8, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_Init(&htim8) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sConfigOC.OCMode = TIM_OCMODE_PWM1;//PWM工作模式1
      sConfigOC.Pulse = 0;
      sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
      sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
      sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
      sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
      sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
      if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
      {
        Error_Handler();
      }
      sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
      sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
      sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
      sBreakDeadTimeConfig.DeadTime = 0;
      sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
      sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
      sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
      if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM8_Init 2 */
    /* 配置触发源 */
      HAL_TIMEx_ConfigCommutationEvent(&htim8, TIM_COM_TS_ITRx, TIM_COMMUTATION_SOFTWARE);
    
      /* 开启定时器通道1输出PWM */
      HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
    
      /* 开启定时器通道2输出PWM */
      HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_2);
    
      /* 开启定时器通道3输出PWM */
      HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
    
      /* USER CODE END TIM8_Init 2 */
      HAL_TIM_MspPostInit(&htim8);
    
    }
    

    在时基结构体中我们设置定时器周期参数为(5600)-1,时钟预分频器设置为 (2) - 1,频率为:168MHz/PWM_PERIOD_COUNT/PWM_PRESCALER_COUNT=15KHz, 使用向上计数方式。

    在输出比较结构体中,设置输出模式为PWM1模式,通道输出高电平有效,设置脉宽为0。

    触发源配置为软件触发。

    最后使用HAL_TIM_PWM_Start函数开启计数器,使能PWM输出。

  • 电机控制部分motor.c

//停止PWM输出
void stop_pwm_output(void)
{
  /* 关闭定时器通道1输出PWM */
  __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);

  /* 关闭定时器通道2输出PWM */
  __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);
  
  /* 关闭定时器通道3输出PWM */
  __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);
  
  HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂
  HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
  HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
}

//设置PWM输出的占空比
void set_pwm_pulse(uint16_t pulse)
{
  /* 设置定时器通道输出 PWM 的占空比 */
	bldcm_pulse = pulse;
}



//使能霍尔传感器
void hall_enable(void)
{
  /* 使能霍尔传感器接口 */
  __HAL_TIM_ENABLE_IT(&htim5, TIM_IT_TRIGGER);
  __HAL_TIM_ENABLE_IT(&htim5, TIM_IT_UPDATE);
  
  HAL_TIMEx_HallSensor_Start(&htim5);

  HAL_TIM_TriggerCallback(&htim5);   // 执行一次换相
}
//失能霍尔传感器
void hall_disable(void)
{
  /* 禁用霍尔传感器接口 */
  __HAL_TIM_DISABLE_IT(&htim5, TIM_IT_TRIGGER);
  __HAL_TIM_DISABLE_IT(&htim5, TIM_IT_UPDATE);
  HAL_TIMEx_HallSensor_Stop(&htim5);
}

uint8_t get_hall_state(void)
{
  uint8_t state = 0;
  
#if 1
  /* 读取霍尔传感器 U 的状态 */
  if(HAL_GPIO_ReadPin(GPIOH, GPIO_PIN_10) != GPIO_PIN_RESET)
  {
    state |= 0x01U << 0;
  }
  
  /* 读取霍尔传感器 V 的状态 */
  if(HAL_GPIO_ReadPin(GPIOH, GPIO_PIN_11) != GPIO_PIN_RESET)
  {
    state |= 0x01U << 1;
  }
  
  /* 读取霍尔传感器 W 的状态 */
  if(HAL_GPIO_ReadPin(GPIOH, GPIO_PIN_12) != GPIO_PIN_RESET)
  {
    state |= 0x01U << 2;
  }
#else
  state = (GPIOH->IDR >> 10) & 7;    // 读 3 个霍尔传感器的状态
#endif

  return state;    // 返回传感器状态
}

  int update = 0;     // 定时器更新计数

//霍尔传感器触发回调函数
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
{
  /* 获取霍尔传感器引脚状态,作为换相的依据 */
  uint8_t step = 0;
  step = get_hall_state();

	#if 0
  if(get_bldcm_direction() == MOTOR_FWD)
  {
    step = 7 - step;        // 根据换向表的规律可知: REV = 7 - FWD;
  }
	#endif
	if(get_bldcm_direction() == MOTOR_FWD)
		{
			switch(step)
			{
				case 1:    /* U+ W- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);            // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);         // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, bldcm_pulse);   // 通道 1 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 2:     /* V+ U- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);          // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);         // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, bldcm_pulse);  // 通道 2 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_SET);      // 开启下桥臂
				
					break;
				
				case 3:    /* V+ W- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);          // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);          // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
					
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, bldcm_pulse);   // 通道 2 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 4:     /* W+ V- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);           // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);         // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
		 
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, bldcm_pulse);   // 通道 3 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_SET);      // 开启下桥臂 
					break;
				
				case 5:     /* U+  V -*/
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);         // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);           // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, bldcm_pulse);   // 通道 1 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 6:     /* W+ U- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);        // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);          // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, bldcm_pulse);      // 通道 3 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_SET);      // 开启下桥臂
					break;
			}
		}
		else
		{
			switch(step)
			{
				case 1:   /* W+ U- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);         // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);          // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, bldcm_pulse); // 通道 3 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 2:    /* U+  V -*/
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);         // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);          // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, bldcm_pulse);   // 通道 1 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 3:   /* W+ V- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);          // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);          // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂
		 
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, bldcm_pulse);  // 通道 3 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_SET);      // 开启下桥臂        

					break;
				
				case 4:    /* V+ W- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);         // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);          // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
					
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, bldcm_pulse);  // 通道 2 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 5:    /* V+ U- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);         // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, 0);       // 通道 1 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, bldcm_pulse);      // 通道 2 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_SET);      // 开启下桥臂
					break;
				
				case 6:    /* U+ W- */
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 0);          // 通道 2 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_14, GPIO_PIN_RESET);    // 关闭下桥臂
				
					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, 0);          // 通道 3 配置为 0
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_13, GPIO_PIN_RESET);    // 关闭下桥臂

					__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, bldcm_pulse); // 通道 1 配置的占空比
					HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_SET);      // 开启下桥臂
					break;
			}
		}
		HAL_TIM_GenerateEvent(&htim8, TIM_EVENTSOURCE_COM);    // 软件产生换相事件,此时才将配置写入	
	  update = 0;
}

//定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (update++ > 1)    // 有一次在产生更新中断前霍尔传感器没有捕获到值
  {
    printf("堵转超时\r\n");
    update = 0;
    
    /* 堵转超时停止 PWM 输出 */
    hall_disable();       // 禁用霍尔传感器接口
    stop_pwm_output();    // 停止 PWM 输出
  }
}

停止PWM输出:void stop_pwm_output();

设置PWM输出的占空比:void set_pwm_pulse();

使能霍尔传感器:void hall_enable();

失能霍尔传感器:void hall_disable();

判断霍尔状态:uint8_t get_hall_state();

霍尔传感器触发回调函数:void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);

定时器更新中断回调函数:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

  • 电机控制函数封装部分bldc.c

    //设置电机速度
    void set_bldcm_speed(uint16_t v)
    {
      bldcm_data.dutyfactor = v;
      
      set_pwm_pulse(v);     // 设置速度
    }
    
    //设置电机方向
    void set_bldcm_direction(motor_dir_t dir)
    {
      bldcm_data.direction = dir;
    }
    
    //获取电机当前方向
    motor_dir_t get_bldcm_direction(void)
    {
      return bldcm_data.direction;
    }
    
    //使能电机
    void set_bldcm_enable(void)
    {
      BLDCM_ENABLE_SD();
    	HAL_Delay(1);
      hall_enable();
    }
    
    //禁用电机
    void set_bldcm_disable(void)
    {
      /* 禁用霍尔传感器接口 */
      hall_disable();
      
      /* 停止 PWM 输出 */
      stop_pwm_output();
      
      /* 关闭 MOS 管 */
      BLDCM_DISABLE_SD();
    }
    
    uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
    {			
    	/*检测是否有按键按下 */
    	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )  
    	{	 
    		/*等待按键释放 */
    		while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);   
    		return 	KEY_ON;	 
    	}
    	else
    		return KEY_OFF;
    }
    

    将电机控制部分再近一步封装,简单明了

    设置电机速度:void set_bldcm_speed(uint16_t v);

    设置电机方向:void set_bldcm_direction(motor_dir_t dir);

    获取电机当前方向:motor_dir_t get_bldcm_direction(void);

    使能电机:void set_bldcm_enable(void);

    禁用电机:void set_bldcm_disable(void);

    按键控制:uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

  • 电机控制函数封装部分bldc.h

    /* 电机 SD or EN 使能脚 */
    #define BLDCM_ENABLE_SD()                     HAL_GPIO_WritePin(SHUTDOWN_GPIO_Port, SHUTDOWN_Pin, GPIO_PIN_SET)      // 高电平打开-高电平使能 
    #define BLDCM_DISABLE_SD()                    HAL_GPIO_WritePin(SHUTDOWN_GPIO_Port, SHUTDOWN_Pin, GPIO_PIN_RESET)    // 低电平关断-低电平禁用
    
    #define KEY_ON	1
    #define KEY_OFF	0
    
    
    /* 电机方向控制枚举 */
    typedef enum
    {
      MOTOR_FWD = 0,
      MOTOR_REV,
    }motor_dir_t;
    
    typedef struct
    {
      motor_dir_t direction;    // 电机方向
      uint16_t dutyfactor;      // PWM 输出占空比
      uint8_t is_enable;        // 使能电机
      uint32_t lock_timeout;    // 电机堵转计时
    }bldcm_data_t;
    
    

    设置电机SD引脚状态,电机方向枚举,电机控制枚举

  • 主函数部分main.c

while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  //启动电机设置占空比10%
	if(Key_Scan(KEY1_GPIO_Port,KEY1_Pin) == KEY_ON)
	{
	  set_bldcm_speed(5500/10);//占空比10%
      set_bldcm_enable();//使能电机
	}
	
	//增大占空比
	if( Key_Scan(KEY2_GPIO_Port,KEY2_Pin) == KEY_ON)
	{
		 ChannelPulse += 100;//增加100占空比
      
      if(ChannelPulse > 5500)
        ChannelPulse = 5500;
      
	  //设置电机速度
      set_bldcm_speed(ChannelPulse);
	}
	
	//减小占空比
	if( Key_Scan(KEY3_GPIO_Port,KEY3_Pin) == KEY_ON)
	{
		 if(ChannelPulse < 5500/10)
        ChannelPulse = 0;
      else
        ChannelPulse -= 100;//100占空比
	  
      //设置电机速度
      set_bldcm_speed(ChannelPulse);
	}
	
	//控制电机方向
	if( Key_Scan(KEY4_GPIO_Port,KEY4_Pin) == KEY_ON)
	{
	  //i是奇数是反转运行,i是偶数是正转运行
      set_bldcm_direction( (++i % 2) ? MOTOR_FWD : MOTOR_REV);
	}
	
	//停止电机
	if( Key_Scan(KEY5_GPIO_Port,KEY5_Pin) == KEY_ON)
	{
	  //禁用电机
	  set_bldcm_disable();
	}
}

5. 步进电机

5.1 步进电机基础知识

  • 步进电机概念

步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超载的情况下电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响。即给电机一个脉冲信号,电机则转过一个步距角。这一线性关系的存在,加上步进电机只有周期性的误差而无累积误差等特点,使得在速度、位置等控制领域用步进电机来控制变的非常的简单。

在这里插入图片描述

  • 步进电机的分类

    • 永磁(PM)步进电机
      转子是永久磁铁,永磁式步进电机的转子用永磁材料制成,转子的极数与定子的极数相同。永磁式步进电机一般为两相,转矩和体积较小,步进角一般为7.5度或15度,其特点是动态性能好、输出力矩大,但这种电机精度差,步矩角大。

    • 反应式(VR)步进电机

      转子是电工纯铁,定子上有绕组、转子由软磁材料组成。反应式步进电机一般为三相,可实现大转矩输出,步进角一般为1.5度,但噪声和振动都很大。其特点是动态性能差效率低发热大,可靠性难保证,这种步进电机已被淘汰

    • 混合式 (HB) 步进电机
      转子是永久磁铁+电工纯铁,混合式步进是指混合了永磁式和反应式的优点。其定子上有多相绕组、转子上采用永磁材料,转子和定子上均有多个小齿以提高步矩精度。常用为两相和五相:两相步进角一般为 1.8 度而五相步进角一般为0.72度。这种步进电机的应用最为广泛,其特点是输出力矩大、动态性能好,步距角小,但结构复杂、成本相对较高。

    在这里插入图片描述

  • 步进电机的相,线、极性

    • 相指步进电机有几个线圈(绕组)。常见2相、4相电机。

    • 线: 指步进电机有几个接线口 (多少根线)。

    • 极性:分为单极性和双极性。如果步进电机的线圈是可以双向导电的,那么这个步进电机就是双极性的;相反,如果步进电机的线圈是只允许单向导电的,那么这个步进电机就是单极性的。

  • 上面的三个只要知道其中两个,就可以推断出第三个。

如:五线四相步进电机就是有5个接线口,4个线圈。由于有五个接线头;即接线头的个数是奇数个,也就是说有一个接线头是公共接头,所以它的线圈的导电方式就只允许是单响的,即这个步进电机是单极性的。

这里我们可以把5个接线口分别定义为A、B、C、D和COM端,这A和COM构成组线圈 (绕组) ,B、C、D类似。电流只能从A流向Com端,所以是单极性的。

在这里插入图片描述

四线双极性步进电机就是有4个接线口,导电方式是允许双向的。由于有四个接线口且导电方式是双向的,所以这个步进电机是两相的。电机线圈有两组:A和A-,B和B-,电流可以从A进A-出,也可以从A-进A出,B绕组类似这样电流是可以双方向流的,所以是双极性。

在这里插入图片描述

  • 步进电机的驱动方式

    步进电机的步进方式:单拍、双拍、单双拍

    • 单拍(四拍工作方式)

      单拍工作方式就是说每次只给一个线圈通电,通过改变每次通电的线圈从而使步进电机转动。

      • 对应五线四相步进电机,在单拍工作方式下,线圈的通电方式依次是: A-COM、B-COM、C-COM、D-COM.

      • 对于四线双极性步进电机,在单拍工作方式下,线圈依次是: A-A‘、B-B’、A’-A、B‘-B

      在这里插入图片描述

    • 双拍(四拍工作方式)

      双拍工作方式就是:每次给两个线圈通电,通过改变通电的线圈从而使步进电机转动

      • 对于五线四相步进电机,在双拍工作方式下,线圈的通电方式依次是: .A-COM与B-COM、B-COM与C-COM、C-COM与D-COM、D-COM与A-COM。
      • 对于四线双极性步进电机,在双拍工作方式下,线圈的通电方式依次是: A-A’与B-B‘、B-B’与A’-A、A’-A与B‘-B、B’-B与A-A‘;
    • 双拍是为了增加更大的转矩,同时闭合两个,磁场会更大

    • 单双拍(八拍工作方式)

      单双拍工作方式就是单拍工作方式和双拍工作方式交替进行。

      • 对于五线四相步进电机: A-COM、A-COM与B-COM、B-COM、B-COM与C-COM、C-COM、CCOM与D-COM、D-COM、D-COM与A-COM;
      • 对于四线双极性步进电机: A-A’、A-A‘与B-B’、B-B‘、B-B’与A‘-A、A’-A、A‘-A与B’-B、B‘-B、B’-B与A-A‘

      在这里插入图片描述

  • 步距角

    • 当步进电机切换一次定子绕组的激磁电流时,转子就旋转一个固定角度即步距角。步距角由切换的相电流产生的旋转力矩得到
    • 步距角大小与控制绕组的相数、转子齿数和通电方式有关。步距角越小,运转的平稳性越好。
    • 步距角:对应一个脉冲信号,电机转子转过的角位移用θ表示。θ=360度/(转子齿数运行拍数),以常规二、四相,转子齿为50齿电机为例。四拍运行时步距角为θ=360度/(504)=1.8度(俗称整步),八拍运行时步距角为θ=360度/(50*8)=0.9度(俗称半步)
      • 转子齿数越多,步距角0。越小;
      • 定子相数越多,步距角0。越小;
      • 通电方式的节拍越多,步距角0。越小;

    在这里插入图片描述

    • m为运行的拍数。常见两相电机有4拍和8拍运行方式
    • zr为转子齿数。常见的两相步进电机齿数为50:
  • 单相步进电机、

单相步进电机是在一个线圈骨架上缠绕环形线圈,给它通以正负交变的电流,每切换次电流就按固定方向走一步。由于转子磁路所通过的磁导(磁阻的倒数,表示磁通流过的容易程度)变大为其转动方向,故单相步进电机只能接一个方向运动。为使转动方向确定,磁导采取了多种措施,例如使定子磁极宽于转子,定子与转子之间的工作气隙不均匀转动方向为磁阻小的方向。下图为单相步进电机的转动原理。

在这里插入图片描述

图(a)定子绕组通正电流,定子磁极产生N和s极,转子的N和S极被定子磁极吸引,停在图示位置。当定子电流由正变负时,在切换过程中,电流接近于零,定子对转子的吸引力接近零,此时转子磁通产生的转矩为主,如图(b)所示,转子的磁通要走气隙最小的路径,故转子在磁通力矩的作用下,沿箭头方向运动到转子磁极轴线(N和S极的中心线)正对气隙最小处停止。

当定子绕组为负电流时,如图©所示,定子磁极的极性反转,转子磁极受到定子N和S极的斥力和引力作用,沿箭头方向运动,直到定转子磁极轴线重合时转子停止运动。加在绕组上的电流再次变换方向由负变正时,电流过零变正,则转子经过图(d)向图(a)移动,步距角为180°上述动作反复进行,电机转子就能继续转动。

  • 两相步进电机

    两相步进电机最简单的构成为Nr=1(转子磁极对数) 的情况,电机结构如下图所示。一般两相电机定子磁极数为4的倍数,至少是4,实际中一般为8极数(4对极) 。转子为N极与S极各一个的两极转子。

    定子一般用硅钢片叠压制作,定子磁极数为4极相当于一相绕组占两个极,A相两个极在空间相差180°,B相两个极在空间也相差180°。电流在相绕组内正负流动 (双极性驱动) ,A相与B相电流的相位相差90°,两相绕组中矩形波电流交替流过即两相电机的定子,在Nr=1时,空间相差90°,时间上电流相差90°,在定子上产生旋转磁场转子被旋转磁场吸引,随旋转磁场同步旋转。

在这里插入图片描述

上图中表示两相步进电机的结构 (PM型)及其运行原理,从图(a)到图(b)顺时针旋转90°依次图)、(d)均旋转90,依次不断运转成为连续旋转。
以上图为例,A相有两个线圈,单向电流交替流过两个线圈,也可产生相反的磁通方向,此方式称为单极 (uni-plar)型线圈,比如图(a):两个线圈电流都是从左边进去,从右边出来。

如下图b所示线圈内部只流过单方向电流,此线圈称为单极型线圈,另一种,线圈内流过正、反方向电流的线圈称为双极型线圈。单极型线圈可以取代上图所示双极型线圈;运行时具有相同的步距角。

在这里插入图片描述

(1)双极性电机,电流在2个线圈流动时序为 AA’-> BB‘->A’A-> B‘B;
(2)而单极性电机线圈中电流的流动方向是固定的,其通电时序为VA->VC->VB->VD;
(3)单极性电机的驱动器较双极性电机驱动器简单,但单极性电机的输出方矩较小。

  • 步进电机定子相数

下图中,定子的结构:两相为8个主极、三相为6个主极、五相为10个主极,为最简单的结构。
三相步进电机定子线圈的主极数为三的倍数,故三相步进电机的定子主极数为、6、9、12等
当转子齿数为50时,对两相、三相、四相、五相电机而言,单拍运行,得到步距角为:两相(4拍驱动)为1.8°,三相为(6拍驱动) 1.2°,四相(8拍驱动)为0.9°,五相(10拍驱动)为0.72°

在这里插入图片描述

5.2 步进电机工作原理

  • 当电流流过定子绕组时,定子绕组产生一矢量磁场。该磁场会带动转子旋转一角度,使得转子的一对磁场方向与定子的磁场方向一致。当定子的矢量磁场旋转一个角度。转子也随着该磁场转一个角度。每输入一个电脉冲,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转。所以可用控制脉冲数量、频率及电动机各相绕组的通电顺序来控制步进电机的转动。

  • 单极性驱动原理

    • 单极性驱动分为整步驱动和半步驱动两大类。
      (1)下图是单极性整步驱动的工作原理图首先如图 (a) 所示,给A线圈通上从A2到A1的电流,A线圈产生上南 (S) 下北(N) 的磁极,转子的南极 (S) 被吸引到A线圈的下方;接着如图(b) 所示给B线圈通上从B2到B1的电流,转子的南极被吸引到B线圈的左边;然后如图 ©所示,给C线圈通上从C2到C1的电流,转子的南极被吸引到C线圈的上方;最后如图 (d) 所示,给D线圈通上从D2到D1的电流,转子的南极被吸引到 D线圈的右方

在这里插入图片描述

这样在A->B->C->D的通电顺序下转子将分4步顺时针旋转,如果通电顺序改成D->C->B->A,转子将逆时针旋转在这个过程中,每个线圈的电流方向固定从“2”到“1”,例如从A2到A1,所以称作单极性驱动;转子从一个线圈一步到位地转到另一个线圈,每一步转过的角度是90°,所以称作整步驱动;在任意时刻,只给一个线圈通电,其他三个线圈都没有被通电,单极性整步驱动比后面介绍的双极性整步驱动的转矩要小。

(2)下图是单极性半步驱动的方式,它和所示的整步驱动相比,在两个整步之间插入了一个“半步”,例如图 (b) 所示,给A、B线圈同时通电,电流方向分别从A2到A1和B2到B1,A、B线圈在靠近转子的一端,同时形成磁力相等的北极(N),转子的南极 (s) 在磁力的平衡作用下,停在A和B的正中央。

在这里插入图片描述

这样在A->AB->B->BC->C->CD->D->DA的通电顺序下,转子将分8步顺时针旋转,如果通电顺序改成DA->D->CD ->C->BC->B->AB ->A,转子将逆时针旋转。在这个过程中,每个线圈的电流方向也是固定从“2”到“1”,所以也称作单极性驱动;转子每次只走半步,所以称作半步驱动;和整步驱动相45°,半步驱动把一整步分成两个半步来跑电机转起来会更顺畅。

在驱动过程中,为了让转子的机械速度能够跟上定子的通电速度,每驱动一步都要延时一段时间才能驱动下一步,例如在半步驱动中,首先给A线圈通电,接着延时一段时间,等转子转到A线圈处,然后同时给A、B线圈通电,再延时一段时间,等转子转到A、B线圈的中央,如此类推,改变延时的时间,即可改变速度如果延时时间太短,转子还没有转到位就开始驱动下一步,那么转子就会出现失步震荡的情况。步进电机在启动时,如果目标速度较高,必须有加速过程,即延时时间要逐步减少,让电机的速度一步步地提高到目标速度为止。

在这里插入图片描述

对于实际使用的步进电机,为了减少每步的角度(就是减小步距角) ,一般通过增加定子线圈转子磁铁的数目而实现,上图是永磁式步进电机的等效结构图,整步从90° 减少到 15° (360/(8*4) )它的转子含有6个磁铁,定子含有A一H等8个线圈,其中A和C、E和G、B和D、F和H分成4组 (8拍工作) ,每组各自并联在一起,工作时转子可以等效成一对南北极的磁铁,定子等效成4个线圈绕组,分析方法相同。

  • 双极性步进电机驱动原理
    • 双极性驱动分为整步驱动、半步驱动和细分驱动三大类。
      (1)下图是双极性整步驱动的工作原理图其中A2端和C2端、B2端和D2端在生产电机时,已经在电机内部联通。首先如图(a) 所示,给C线圈和A线圈通上从C1到A1的电流,C线圈和A线圈同时产生上南(s) 下北 (N) 的磁极,转子被吸引到上南 (s) 下北 (N) 的位置;接着如图(b) 所示,给D线圈和B线圈通上从D1到B1的电流,转子被吸引到左北右南的位置;然后如 © 所示,给A线圈和C线圈通上从A1到C1的电流,转子被吸引到上北下南的位置; 最后如 d) 所示,给B线圈和D线圈通上从B1到D1的电流,转子被吸引到左南右北的位置;

在这里插入图片描述

这样在CA->DB->AC->BD的通电顺序不,转子将分4步顺时针旋转,如果通电顺序改成BD->AC->DB->CA,转子将逆时针旋转。在这个过程中,每个线圈的电流方向是双向改变的,例如A线圈的电流可以从A2到A1,也可以从A1到A2所以称作双极性驱动;和单极性整步驱动一样,转子也是从一个线圈一步到位地转到另一个线圈,每一步转过的角度也是90°,所以也称作整步驱动; 在任意时刻和单极性整步驱动相比,有两个线圈被通电,所产生的转矩更大

(2)下图是双极性半步驱动的方式,它和所示的整步驱动相比,在两个整步之间插入了一个“半步”,例如图 (b) 所示给A、B、C、D四个线圈同时通电,电流方向分别从C1到A1和D1到B1,A、B线圈在靠近转子的一端,同时形成磁力相等的北极 (N) , C、D线圈在靠近转子的一端,同时形成磁力相等的南极 (s) ,转子在磁力的平衡作用下,停在一个整步的正中央。

在这里插入图片描述

这样在CA->CA/DB->DB->DB/AC->AC->AC/BD->BD->BD/CA的通电顺序下,转子将分8步顺时针旋转,如果通电顺序改成BD/CA->BD->AC/BD ->AC->DB/AC->DB->CA/DB->CA,转子将逆时针旋转。在这个过程中,每个线圈的电流方向也是双向改变的,所以也称作双极性驱动;转子每次只走半步45°,所以称作半步驱动;在任意时刻,和单极性半步驱动相比,被通电的线圈的数量多了一倍(2个或4个),所产生的转矩更大

  • 细分驱动简介

如下图所示,假设流过A、C线圈的电流大小为la;流过B、 D线圈的电流大小为Ib,因为磁场强度和电流的大小成正比,如果la等于lb,转子将停在相邻两个线圈的中央,如果la不等于lb,那么转子将停在电流较大的一侧,转子在停住时,和水平方向的夹角是:

在这里插入图片描述

在这里插入图片描述

从中可以看出:改变a和b的比例即可控制转子在一个整步中的任何位置停住。
细分驱动的原理就是:改变定子线圈的电流比例,让转子在旋转过程中,可以停靠在一个整步中不同位置,把一个整步分成多个小步来跑。
细分驱动具有:转动顺畅、精度高.转矩大的特点,但控制复杂,一般需要专用芯片来实现,例如东芝公司的TB67S10xA系列步进电机细分驱动芯片,最多可以把1个整步分成 32 个小步。

  • 步进电机的几个概念

    • 电机损耗(发热原理)

      通常见到的各类电机,内部都是有铁芯和绕组线圈的。绕组有电阻,通电会产生损耗,损耗大小与电阻和电流的平方成正比,这就是我们常说的铜损,如果电流不是标准的直流或正弦波,还会产生谐波损耗,铁心有磁滞涡流效应,在交变磁场中也会产生损耗,其大小与材料,电流,频率,电压有关,这叫铁损
      铜损和铁损都会以发热的形式表现出来,从而影响电机的效率。步进电机一般追求定位精度和力矩输出,效率比较低,电流一般比较大,且谐波成分高,电流交变的频率也随转速而变化,因而步进电机普遍存在发热情况,且情况比一般交流电机严重。(可达80°左右)

    • 保持转矩
      是指步进电机通电但没有转动时,定子锁住转子的力矩。它是步进电机最重要的参数一,通常步进电机在低速时的力矩接近保持转矩。由于步进电机的输出力矩随速度的增大而不断衰减,输出功率也随速度的增大而变化,所以保持转矩就成为了衡量步进电机最重要的参数之一。比如,当人们说 2Nm 的步进电机,在没有特殊说明的情况下是指保持转矩为2Nm 的步进电机。

    • 失步 (丢步)
      电机运转时运转的步数不等于理论上的步数,称之为失步。一般发生在启动停机阶段,特别如果给的脉冲频率过高也会产生丢步情况。

    • 最大空载起动频率
      电机在某种驱动形式、电压及额定电流下,在不加负载的情况下;能够直接起动的最大频率。

    • 最大空载运行频率:
      电机在某种驱动形式,电压及额定电流下,电机不带负载的最高转速频率

    • 运行矩频特性
      电机在某种测试条件下测得运行中输出力矩与频率关系的曲线称为运行矩频特性,这是电机诸多动态曲线中最重要的,也是电机选择的根本依据。如下图:

    在这里插入图片描述

电机一旦选定,电机的静力矩(保持转矩)确定,而动态力矩却不然,电机的动态力矩取决子电机运行时的平均电流(而非静态电流),平均电流越大;电机输出力矩越大,即电机的频率特性越硬。

在这里插入图片描述

其中,曲线3电流最大、或电压最高;曲线1电流最小、或电压最低,曲线与负载的交点为负载的最大速度点。要使平均电流大,尽可能提高驱动电压,或采用小电感大电流的电机;

5.3 57步进电机

在实际工业设备中一般使用较大尺寸的步进电机,它们有更大的扭矩。根据电机外尺寸的不同,可以分为42、57、86、110步进电机;另外,同个尺寸电机可以有不同的机身长度,影响转矩大小。电机正常旋转电流都是1A以上的,一般用专用的步进电机驱动器

在这里插入图片描述

用得最多,最常见的是两相4线步进电机,步距角为1.8°即旋转一圈需要200个脉冲;

在这里插入图片描述

  • 两相四线双极性步进电机线序

    黑色A+ 绿色A- 红色B+ 蓝色B-

    注意:每个电机的厂家给的线序是不一样的,所以不是固定不变的;

在这里插入图片描述

在这里插入图片描述

  • 57BYG250B步进电机频率特性

步进电机一般适用于低速场合,可以认为转矩是几乎保持不变的;

在这里插入图片描述

  • 步进电机驱动器

类似28BYJ-48这些小尺寸、低转矩的步进电机使用ULN2003这类驱动芯片并配合控制器使用是可行的,但在实际工业应用中,经常使用到42、57、86、110等等系列型号步进电机,这些步进电机需要高电压、大电流、大功率驱动电路才能使其正常运转,另外,为充分实现发挥细分驱动优势,往往需要更加复杂的驱动电路。好在,很久之前很多半导体公司已经看到了这部分商机,专门为驱动步进电机设计了驱动芯片,种类繁多,功能齐全,同时也使得我们控制步进电机运动非常简单。

下图是28BYJ-48步进电机:

在这里插入图片描述

当然,步进电机也可以类似之前介绍过的直流有刷电机驱动一样,直接搭建Mos桥电路驱动,根据之前的节拍控制方法实现步进电机旋转,只是如果自己搭建电路驱动,程序处理会复杂一些,特别要实现细分驱动就需要更复杂的程序实现,另外硬件成本也没有优势。现有的专用步进电机芯片种类繁多,可根据电机参数选择对应的驱动芯片,价格也非常有优势,重点是驱动起来容易很多。

  • 步进电机细分驱动芯片

TOSHIBA (东芝)公司的TB67S109A芯片是一种配备PWM斩波器的两相双极步进电机(也就是两相四线步进电机)驱动芯片。内置时钟解码器。采用BiCD工艺制作,额定值为50v/4.0A。允许全步(也就是我们上边说的整步)、半步1/4、1/8、1/16、1/32步 (细分) 运行,即细分最高为32。

TB67S109A芯片的内部逻辑电路见图。

在这里插入图片描述

在这里插入图片描述

  • 步进电机驱动芯片分析

    • TB67S109A芯片功能

(1)功能说明

在这里插入图片描述

(2)步分变率选择功能

在这里插入图片描述

在切换 DMODE0,1,2 时,建议将 RESET 信号设置为低(这意味着将电角设置为初始状态)。在实际操作是将驱动器断电后在重新选择分频。

(3)步分变率设置与初始角

四拍工作方式

在这里插入图片描述

双极性电机,电流在2个线圈流动时序为A-A’与B-B‘、B-B’与A‘-A、A’-A与B‘-B、B’-B与A-A‘;(A为+100%,A’为-100%,B也相同)
Mo引脚为角度信号输出,CLK每触发一次上升沿,MO就执行高电平,MO完成一次整步就输出一个低电平脉冲,其它三拍就是高电平;

八拍工作方式

在这里插入图片描述

半步分辨率(类型A)时序:A-A‘、A-A’与B-B‘、B-B‘、B-B’与A’-A、A‘-A、A’-A与B‘-B、B’-B、 B‘-B与A-A’

该时序图中所示的MO是输出,是MO引脚上拉时的情景;

八拍工作方式

在这里插入图片描述

半步分辨率(类型B)是在类型A的基础上增加了71%的电流,分成了两个阶梯。

十六拍工作方式

在这里插入图片描述

四分之一步分辨率是在半步分辨率(类型B)基础上又增加了31%的电流,分成了四个阶梯;

在这里插入图片描述

(4)步设置与电流百分比

在这里插入图片描述

(5)RESET功能

在这里插入图片描述

(6)LO(错误检测信号)输出功能
在应用热关断(TSD)或过电流关断(ISD)时,LO 电压将被切换为低(GND)电平。

在这里插入图片描述

LO 为开漏输出引脚。LO 引脚需被上拉至 3.3V/5.0V 电平才能实现正常功能。在常规运行期间,该LO 引脚电平将保持为高(VCC 电平)。在应用错误检测(TSD,ISD)时,LO 引脚将显示低(GND)电平。(也就是说没有过温,没有过流LO引脚就是低电平)

  • 步进电机驱动器

TB67S109A步进电机驱动器是一款专业的两相步进电机驱动器,如下图。可实现正反转控制,通过3位拨码开关选择7档细分控制(1,2/A,2/B,4,8,16,32,),通过3位拨码开关选择8档电流控制(0.5A;1A,1,5A,2A,2,5A,2.8A,3.0A,3.5A) 。适合驱动57、42型两相混合式步进电机。能达到低振动、小噪声、高速度的效果驱动电机。(如果是86,110的步进电机就要选择电流更大的驱动器,这款驱动器最大才3.5A)

在这里插入图片描述

  • 步进电机接线

电机绕组连接
A+:连接电机绕组A+相。
B+:连接电机绕组B+相。
A-:连接电机绕组A-相。
B-:连接电机绕组B-相。
电源电压连接
VCC: 电源正端“+”
GND: 电源负端“-”
注意: 9V-40V。不可以超过此范围,否则会无法正常工作甚至损坏驱动器。
信号输入端
PUL+: 脉冲信号输入正。
PUL-: 脉冲信号输入负。
DIR+: 电机正、反转控制正。
DIR-: 电机正、反转控制负。
EN+: 电机脱机控制正
EN-: 电机脱机控制负。

  • 步进电机信号线(控制线)接线方法

输入信号共有三路,它们是: (1)步进脉冲信号PUL+,PUL-;(2)方向电平信号DIR+,DIR- ;(3)脱机(关闭) 信号EN+,EN-。接口有两种接法,用户可根据 需要采用共阳极接法共阴极接法。实际上,驱动器的输入信号也是用光耦芯片,所以我们驱动目的是让光耦导通与否。

共阳极接法:分别将PUL+,DIR+,EN+连接到控制系统的电源正极上,如果此电源是+5v/3.3v则可直接接入,如果此电源大于+5v,则须外部另加限流电阻R,保证给驱动器内部光藕提供8-15mA的驱动电流,一般如果是24V电压,选择2K欧的电阻。脉冲输入信号通过PUL-接入,方向信号通过DIR-接入,使能信号通过EN-接入。

在这里插入图片描述

共阴极接法: 分别将PUL-, DIR-,EN-连接到控制系统的地线(GND) 上。脉冲输入信号通过PUL+接入,方向信号通过DIR+接入,使能信号通过EN+接入。如果此控制系统信号线是+5v/3.3v 则可直接接入,如果此信号电压大于+5V,则须外部另加限流电阻R。保证给驱动器内部光藕提供8-15mA的驱动电流,一般如果是24V电压,选择2K欧的电阻。

在这里插入图片描述

  • 细分数设定
    细分数是以驱动板上的拨码开关选择设定的,用户可根据驱动器外盒上的细分选择表的数据设定(要求在断电情况下设定) 。
    细分后步进电机步距角按下列方法计算:步距角=电机固有步距角/细分数;
    如:一台固有步距角 为1.8°的步进电机在4细分下步距角为1.8° /4=0.45° , 旋转一圈需要的脉冲数为:360/0.45=800;
    驱动板上拨码开关SW1、SW2、SW3、分别对应S1、S2、S3;

在这里插入图片描述

首先,我们看细分数为1,实际上就是没细分功能,如果给驱动器200个脉冲(对应1.8步距角),就可以使得电机旋转一圈。另外,可以看到要驱动电机旋转一圈的脉冲数与细分数是成正比的。一旦确定了细分数,控制程序也需要根据细分数进行修改,可以说细分数与控制程序是息息相关的。(细分数越大,旋转的越平滑,转速跟我们的细分数是息息相关的。)

  • 电流大小设定
    驱动器输出最大电流。驱动板上拨码开关SW4、SW5、SW6分别对应S4、S5、S6;我们默认选择3.5A电流,即sw4、Sw5和SW6都为OFF。下图是电流大小的示意图,和拨码开关示意图,拨码开关向上为OFF,向下为ON

在这里插入图片描述

电流是根据电机来做选择需要哪个电流

5.3.1 代码部分
  • 定时器部分
uint16_t Toggle_Pulse = 3000;         // 比较输出周期,值越小输出频率越快

void MX_TIM8_Init(void)
{

  /* USER CODE BEGIN TIM8_Init 0 */

  /* USER CODE END TIM8_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM8_Init 1 */

  /* USER CODE END TIM8_Init 1 */
  htim8.Instance = TIM8;
  htim8.Init.Prescaler = 5; //预分配值
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
  htim8.Init.Period = 65535; //重转载值
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim8.Init.RepetitionCounter = 0;
  htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim8) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim8, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_Init(&htim8) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TOGGLE; //选择翻转模式
  sConfigOC.Pulse = Toggle_Pulse; //比较输出周期,值越小输出频率越快
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_OC_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }

步进电机这里我们不使用PWM输出,我们使用定时器的比较输出模式,并采用翻转模式,步进电机基本控制很简单,只需配置定时器,我们就再主函数直接实现;

主函数部分main.c

#define STEPMOTOR_MICRO_STEP      32  // 步进电机驱动器细分,必须与驱动器实际设置对应
uint8_t dir=0; // 0 :顺时针   1:逆时针 
uint8_t ena=0; // 0 :正常运行 1:停机
extern __IO uint16_t Toggle_Pulse; /* 步进电机速度控制,可调节范围为 300 -- 3500 ,值越小速度越快 */
/*
*    当步进电机驱动器细分设置为1时,每200个脉冲步进电机旋转一周
*                        为32时,每6400个脉冲步进电机旋转一周
*    下面以设置为32时为例讲解:
*    pulse_count用于记录输出脉冲数量,pulse_count为脉冲数的两倍,
*    比如当pulse_count=12800时,实际输出6400个完整脉冲。
*    这样可以非常方便步进电机的实际转动圈数,就任意角度都有办法控制输出。
*    如果步进电机驱动器的细分设置为其它值,pulse_count也要做相应处理
*/
__IO uint32_t pulse_count = 0; /*  脉冲计数,一个完整的脉冲会增加2 */
int main(viod)
{
/* 确定定时器 */
  HAL_TIM_Base_Start(&htim8);
  /* 使能中断 关闭比较输出*/
  HAL_TIM_OC_Start_IT(&htim8,TIM_CHANNEL_1);
  TIM_CCxChannelCmd(TIM8,TIM_CHANNEL_1,TIM_CCx_DISABLE);
  
while (1)
  {
 
	if(Key_Scan(KEY1_GPIO_Port,KEY1_Pin) == KEY_ON)
	{
		pulse_count=0;            
		ena=0;          
		TIM_CCxChannelCmd(TIM8,TIM_CHANNEL_1,TIM_CCx_ENABLE);
	}
	

	if( Key_Scan(KEY2_GPIO_Port,KEY2_Pin) == KEY_ON)
	{
		Toggle_Pulse-=50;
		if(Toggle_Pulse<50)  // 最快速度限制
		  Toggle_Pulse=50;
	}
	

	if( Key_Scan(KEY3_GPIO_Port,KEY3_Pin) == KEY_ON)
	{
		Toggle_Pulse+=100;
		if(Toggle_Pulse>3500)         // 最慢速度限制
        Toggle_Pulse=3500;
	}
	

	if( Key_Scan(KEY4_GPIO_Port,KEY4_Pin) == KEY_ON)
	{
			if(dir==0)
		  {
			STEPMOTOR_DIR_REVERSAL();  // 反转
			dir=1;
		  }
		  else
		  {
			STEPMOTOR_DIR_FORWARD();   // 正转
			dir=0;
		  }
	}
	
	

	if( Key_Scan(KEY5_GPIO_Port,KEY5_Pin) == KEY_ON)
	{
		 if(ena==1)
		  {
			STEPMOTOR_OUTPUT_ENABLE();   // 正常运行
			HAL_TIM_OC_Start_IT(&htim8,TIM_CHANNEL_1);
			ena=0;
		  }
		  else
			  
		  {
			STEPMOTOR_OUTPUT_DISABLE();// 停机
			HAL_TIM_OC_Stop_IT(&htim8,TIM_CHANNEL_1);
			ena=1;
		  }
		}
		if(pulse_count >= STEPMOTOR_MICRO_STEP*200*2*10)  // 转动10圈后停机 
		{
		  TIM_CCxChannelCmd(TIM8,TIM_CHANNEL_1,TIM_CCx_DISABLE);
		}
	} 
}

5.4 步进电机加减速

  • 为什么要使用加减速

为什么要使用加减速呢?步进电机有一个很重要的技术参数:空载启动频率,也就是在没有负载的情况下能够正常启动的最大脉冲频率,如果脉冲频率大于该值,步进电机则不能够正常启动,发生丢步或者堵转的情况;或者也可以理解为由于步进脉冲变化过快,转子由于惯性的作用跟不上电信号的变化。所以要使用加减速来解决启动频率低的问题,在启动时使用较低的脉冲频率,然后逐渐的加快频率。

  • 失步和过冲

步进电机因其无需反馈就能对位置(脉冲数) 和速度(脉冲频率) 进行控制而在工业自动化设备中的应用极为广泛。可能在实际应用中,对于速度变化较大的,尤其是加减速频繁的设备。常常发生力矩不足或者失步的现象,而实际上许多案例中步进电机的选型并没有问题,其问题在于负载位置对控制电路没有反馈,步进电机就必须正确响应每次励磁变化,如果励磁频率选择不当,电机不能够移到新的位置,那么实际的负载位置相对控制器所期待的位置出现永久误差,即发生失步现象过冲现象,因此在速度变化较大的步进电机控制系统中,防止失步和过冲是开环控制系统能否正常运行的关键。

失步过冲现象分别出现在步进电机启动停止的时候,一般情况下,系统的极限启动频率比较低,而要求的运行速度往往比较高,如果系统以要求的运行速度直接启动,因为该速度已超过极限启动频率而不能正常启动,轻则可能发生丢步,重则根本不能启动,立生堵转。系统运行起来以后,如果达到终点时立即停止发送脉冲串。令其立即停止,则由于系统惯性作用,电机转子会转过平衡位置,如果负载的惯性很大,会使步进电机转子转到接近终点平衡位置的下一个平衡位置,并在该位置停下。

  • 步进电机曲线加减速

步进电机由数字脉冲信号控制运行的,当脉冲提供给驱动器时,在过短的时间里,控制系统发出的脉冲数太多,也就是脉冲频率过高,将导致步进电机堵转。要解决这个问题一般采用而减速的办法。就是说,在步进电机起步时,要给逐渐升高的脉冲频率,减速时的脉冲频率需要逐渐减低。这就是我们常说的“加减速”方法。

步进电机转速度,是根据输入的脉冲信号的变化来改变的。从理论上讲,给驱动器个脉冲,步进电机就旋转一个步距角(细分时为一个细分步距角)。实际上,如果脉冲信号变化太快,步进电机由于内部的反向电动势的阻尼作用,转子与定子之间的磁反应将跟随不上电信号的变化,将导致堵转和丢步
所以步进电机在高速启动时,需要采用脉冲频率升速的方法,在停止时也要有降速过程,以保证实现步进电机精密定位控制。加速和减速的原理是一样的。

以加速为例:加速过程,是由基础频率(低于步进电机的直接起动最高频率)与跳变频率(逐渐加快的频率,类似加速度的概念) 组成加速曲线(降速过程反之)。跳变频率指步进电机在基础频率上逐渐提高的频率,此频率不能太大,否则会产生堵转和丢步。

加减速曲线(速度变化过程曲线) 一般为直线(梯形) 指数或者“s”型曲线等。对于不同负载、不同转速,需要选择合适的基础频率与跳变频率,才能够达到最佳控制效果通常,完成步进电机的加减速时间为300ms以上。如果使用过于短的加减速时间,对绝大多数步进电机来说,就会难以实现步进电机的高速旋转。

在这里插入图片描述

在这里插入图片描述

5.4.1 梯型加减速
  • 梯形加减速算法特点

梯形加减速一共分为三个阶段,OA 加速部分、AB 匀速部分 和 BC 减速部分。 在 OA 加速过程中,由低于步进电机的启动频率开始启动(模型中由 0 启动),以固定的加速度增加速度到目标值;在 AB 匀速过程中,以最大速度匀速运动; 在 BC 减速部分中,以加速度不变的速度递减到 0; 这种算法是一种在加速过程和减速过程中加速度不变的匀变速控制算法,由于速度变化的曲线有折点,所以在启动、停止、匀速段中很容易产生冲击和振动。

在这里插入图片描述

  • 梯形加减速的原理分析

    • 需用户设置的已知量:
      • 步进的总步数:使用step表示
      • 加速度大小:使用accel表示
      • 减速度大小:使用decel表示
      • 最大速度:电机匀速时的速度值,Max_ω表示
    • 需求解的未知量:
      • 加速段步数:使用n1表示
      • 减速段步数:使用n2表示
      • 下次脉冲周期:使用T表示

    在这里插入图片描述

    加速阶段频率越大,周期越小,减速阶段频率越小,周期越大;

  • 梯形加减速算法实现

    • 下次脉冲周期T求解

    在这里插入图片描述

    t1 :发送第二个脉冲的时刻

    t2 :发送第三个脉冲的时刻

    δt :两个脉冲之间的间隔时间

    c0 :定时器从 t0~t1 时刻的定时器计数值

    c1 :定时器从 t1~t2 时刻的定时器计数值

    tt : 定时器计数周期

    注意:

    C0和C1这里我们用的是整个脉冲的计数值,实际上我们步进电机是使用的翻转模式,所以我们要在整个脉冲的基础上除以2才是我们所需要的计数值;

    tt定时器计数周期是我们已知的,我们以STM32定时器8为例,高级定时器 8 的时钟频率为 168MHZ, 如果将分频值设置为 5,那么定时器的时钟频率则为:ft =168/(5+1)=28MHZ, 相当于计数 28M 次正好为一秒,周期与频率为倒 数关系,所以分频值为 5 的定时器 8 的计数周期tt = 1/ft ;C0和C1的值即为ARR寄存器的值,所以我们在固定的定时器频率下,只需要改变ARR的值,即可改变电机的速度。
    我们的目的是求出:C0、 C1、C2…Cn

    • 已知公式:

    在这里插入图片描述

    • 脉冲时间间隔的精确计算

      • 电机由速度0匀加速运动,转过θ所用的时间tn:

        (1)位移公式

        在这里插入图片描述

      • 电机的位置(旋转的角度)θ:

        (2)位置公式

        在这里插入图片描述

        其中:n为脉冲数,a为步距角

        根据(1)(2)可以推导出电机位置(旋转的角度)θ:

        (3)电机位置公式

        在这里插入图片描述

        其中:θ为转过的角度,ω0为初角速度,ώ为加角速度,tn 为所用的时间

        (4)当ω0 = 0时,简化后电机的位置公式

        在这里插入图片描述

      • 相邻脉冲的时间差值:

        (5)时间间隔Δt公式

        在这里插入图片描述

        其中:Cn表示定时器计数器相邻两脉冲间的计数值,tt表示定时器的时钟周期,定时器频率的倒数

        假设相邻两脉冲间转过的角度分别用θt和θt−1表示,所用时间用tn和 tn−1表示,则Δt根据公式(4)也可以表示为:

        在这里插入图片描述

      上式θt和θt−1根据公式(2)可得出θt = (n + 1)a,θt−1 = na;

      代入则可得出公式(6):

      在这里插入图片描述

      其中:n表示转过θt−1角度时,所用的脉冲数
      此处计算时,让θt = (n + 1)a,θt−1 = na;,而不是 θt = na,θt−1 = (n -1),是因为是两脉冲之间的时间间隔,θt−1 的脉中数n最小也应该等于1,所以在n为正整数的情况下,采用θt = (n + 1)a,θt−1 = na更符合实际情况。

      根据公式(5)(6)可得出:

      在这里插入图片描述

      跟据以上公式可得出:

      (7)C0公式

      在这里插入图片描述

      根据公式(7)就可以求得出Cn的公式:

      在这里插入图片描述

      再根据公式(7)在这里插入图片描述
      可求得:

      (8)第n个脉冲周期定时器计数值公式

      在这里插入图片描述

      但是由于此公式得开两次根号,微处理器的计算能力有有限,所以我们引入泰勒公式的特例——麦克劳林公式化简;

      麦克劳林公式

      在这里插入图片描述

      对Cn进行一些转换,以使用简单的计算方法得出Cn,下面是其推导过程:

      在这里插入图片描述

      右边是我们舍去无穷小余项,化简后所得到的式子;我们再将此式子化简得到如下公式;

      (9)化简后公式

      在这里插入图片描述

      这样我们就可以通过公式(7)(9)推导出每个周期内定时器计数值,及每步频率;但是由于我们公式(9)在化简过程中我们忽略了无穷小余项,所以在当n=1时有0.4485的偏差,我们可以将C0也就是公式(7)乘一个系数0.676来解决这个误差;

      (10)乘0.676后的C0公式

      在这里插入图片描述

      由之前我们推算过C0等于:

      在这里插入图片描述

      为了方便我们在代码中的实现我们将进行以下操作:

      C0改写为:

      在这里插入图片描述

      为了减小我们浮点数的一个干扰我们以下进行一个放大缩小操作(目的就是兼容大部分单片机,同时增加运算速度):

      T1_FREQ_148 :0.676*ft/10

      A_SQ :2 α *100000

      (11)C0放大缩小后的公式

      在这里插入图片描述

      定时器的计数值的具体确定

      在这里插入图片描述

      在加速和减速过程中都需要重新计算下一步的时间 。但是计算过程中可能有除不尽的数,所以需要另外把余数部分加上去(提高间隔时间的精度和准确性)

      在这里插入图片描述

      • new_step_delay :下一个脉冲周期的Cn

      • step_delay :上一次脉冲周期的Cn-1

      • accel_count : 记录第几个脉冲n

      • rest就是保存除不尽的余数参与下一个计算

    • 匀速阶段的计数值求法:

      • 由已知公式:

        在这里插入图片描述

        在匀速阶段速度值是我们设置的所以已知,ft定时器的计数频率已知,步距角已知,所以可以求得匀速阶段时的计数值,我们这里定义一个:A_T_x10

在这里插入图片描述

  即:

在这里插入图片描述

  • 加/减速段步数求解

    • 在加速过程中,有两种场景计算速度属性:

      ①未达到最大速度就要开始减速

      ②持续加速直到我们设置的最大速度(匀速阶段)

      在这里插入图片描述

    • 两个场景取决于我们设置的四个参数:

      1)目标步数,用step表示

      2)加速度,用accel表示

      3)减速度,用decel表示

      4)最大速度,用Max_ω表示

      这里我们可以想到一个问题:如何区分这两种情况?

      我们设置两个交点一个是accel_lim,另一个是max_s_lim,通过比较两个的大小来区分,如图当max_s_lim >accel_lim是第一种情况, accel_lim > max_s_lim是第一种情况;

      目的:到了这里我们求得不单单是加减速的步数了还有,accel_lim和max_s_lim,但是图一我们求得的accel_lim就是我们的加速度的步数,图二max_s_lim就是我们的加速度的步数,所以我们要求的也就是三个啦;

      • 加减速度与步数的关系( ①最大速度偏大,加速度偏小,max_s_lim >= accel_lim)

        在这里插入图片描述

        从图中可知,加速段末速度与减速段的初速度相等,即:

        在这里插入图片描述

        此时t1为加速段整个过程消耗的时间,t2则是减速度过程消耗的时间,从上面模型可知,该运动符合物理学中匀加/减速运动,即我们的位移公式:

        在这里插入图片描述

在这里插入图片描述
将公式①和②结合求得:
在这里插入图片描述
n是我们的步数,所以上述公式就是加速段乘我们加速段的步数,减速段乘减速段的步数;
因为n1和n2都未知,但是n1+n2=step是可知的,所以这里还需要做一个技巧性处理:

在这里插入图片描述
求解出了加速段脉冲数n1,已知总的脉冲数step不变,则可知减速段n2:
在这里插入图片描述
“负号” 是因为加速和减速度方向相反;这样我们就求出了三个还差一个max_s_lim。
max_s_lim求法
在这里插入图片描述
我们的加速度有啦,最大速度有啦,我们就可以求出t啦,求出t了我们就可以求出我们的位移啦:
在这里插入图片描述
位移知道啦我们也就可以求出我们的max_s_lim:
在这里插入图片描述

  • 加减速度与步数的关系( ②最大速度偏小,加速度偏大,max_s_lim < accel_lim ) 在这里插入图片描述
    这里与我们前面求法一样,同样max_s_lim也是和最大速度末速度相交,我们的初速度有啦末速度有啦,也能求出t,t有了就能求出我们位移,位移有啦就得到了我们加速段的步数;
    在这里插入图片描述
    在这里插入图片描述
    结合③和④,得出加速段的步数结果:

在这里插入图片描述
求解减速度步数以及accel_lim
同样这里也是加速段末速度与减速段初速度相等,前面我们求得了max_s_lim,将max_s_lim代入我们求得了减速段的步数啦;

由于加速段的末速度等于减速段的初速度:

在这里插入图片描述所以减速段的步数:
在这里插入图片描述
接着就是求accel_lim如第一种情况一样 :
现在我们的加速段步数有啦减速段步数有啦,max_s_lim有啦,就剩accel_lim,同样accel_lim也是加速段的末速度等于减速段的初速度的公式的,但是max_s_lim不等于我们的加速段步数,所以加减速的步数n1和n2都未知,但是n1+n2=step是可知的,所以我们这里同样还需要做一个技巧性处理:
在这里插入图片描述
不同的图像,但是公式是一样的。

  • 定时器中断处理流程

定时器中断产生步脉冲并且只有在步进电机移动时进入,四个不同运动的状态,分别是stop——accel——run——decel——stop,如图所示:

在这里插入图片描述

在定时器中断实现状态机功能:

在这里插入图片描述

梯形加减速整体流程图:

在这里插入图片描述

5.4.1.1 代码部分

定时器部分

定时器部分参考基本控制定时器部分,与之一样;

电机控制部分T_Speed.c

speedRampData srd               = {STOP,CW,0,0,0,0,0};         // 加减速曲线变量
__IO int32_t  step_position     = 0;           // 当前位置
__IO uint8_t  MotionStatus      = 0;           //是否在运动?0:停止,1:运动

/**
  * 函数功能: 相对位置运动:运动给定的步数
  * 输入参数: step:移动的步数 (正数为顺时针,负数为逆时针).
              accel  加速度,实际值为accel*0.025*rad/sec^2
              decel  减速度,实际值为decel*0.025*rad/sec^2
              speed  最大速度,实际值为speed*0.05*rad/sec
  * 返 回 值: 无
  * 说    明: 以给定的步数移动步进电机,先加速到最大速度,然后在合适位置开始
  *           减速至停止,使得整个运动距离为指定的步数。如果加减速阶段很短并且
  *           速度很慢,那还没达到最大速度就要开始减速
  */
void STEPMOTOR_AxisMoveRel( int32_t step, uint32_t accel, uint32_t decel, uint32_t speed)
{  
  __IO uint16_t tim_count;
  // 达到最大速度时的步数
  __IO uint32_t max_s_lim;
  // 必须要开始减速的步数(如果加速没有达到最大速度)
  __IO uint32_t accel_lim;

  if(MotionStatus != STOP)    // 只允许步进电机在停止的时候才继续
    return;
  if(step < 0) // 步数为负数
  {
    srd.dir = CCW; // 逆时针方向旋转
    STEPMOTOR_DIR_REVERSAL();
    step =-step;   // 获取步数绝对值
  }
  else
  {
    srd.dir = CW; // 顺时针方向旋转
    STEPMOTOR_DIR_FORWARD();
  }
  
  if(step == 1)    // 步数为1
  {
    srd.accel_count = -1;   // 只移动一步
    srd.run_state = DECEL;  // 减速状态.
    srd.step_delay = 1000;	// 短延时	
  }
  else if(step != 0)  // 如果目标运动步数不为0
  {

    // 设置最大速度极限, 计算得到min_delay用于定时器的计数器的值。
    // min_delay = (alpha / tt)/ w
    srd.min_delay = (int32_t)(A_T_x10/speed);

    // 通过计算第一个(c0) 的步进延时来设定加速度,其中accel单位为0.1rad/sec^2
    // step_delay = 1/tt * sqrt(2*alpha/accel)
    // step_delay = ( tfreq*0.676/10 )*10 * sqrt( (2*alpha*100000) / (accel*10) )/100
    srd.step_delay = (int32_t)((T1_FREQ_148 * sqrt(A_SQ / accel))/10);

    // 计算多少步之后达到最大速度的限制
    // max_s_lim = speed^2 / (2*alpha*accel)
    max_s_lim = (uint32_t)(speed*speed/(A_x200*accel/10));
    // 如果达到最大速度小于0.5步,我们将四舍五入为0
    // 但实际我们必须移动至少一步才能达到想要的速度
    if(max_s_lim == 0){
      max_s_lim = 1;
    }

    // 计算多少步之后我们必须开始减速
    // n1 = (n1+n2)decel / (accel + decel)
    accel_lim = (uint32_t)(step*decel/(accel+decel));
    // 我们必须加速至少1步才能才能开始减速.
    if(accel_lim == 0){
      accel_lim = 1;
    }

    // 使用限制条件我们可以计算出减速阶段步数
    if(accel_lim <= max_s_lim){
      srd.decel_val = accel_lim - step;
    }
    else{
      srd.decel_val = -(max_s_lim*accel/decel);
    }
    // 当只剩下一步我们必须减速
    if(srd.decel_val == 0){
      srd.decel_val = -1;
    }

    // 计算开始减速时的步数
    srd.decel_start = step + srd.decel_val;

    // 如果最大速度很慢,我们就不需要进行加速运动
    if(srd.step_delay <= srd.min_delay){
      srd.step_delay = srd.min_delay;
      srd.run_state = RUN;
    }
    else{
      srd.run_state = ACCEL;
    }    
    // 复位加速度计数值
    srd.accel_count = 0;
  }
  MotionStatus = 1; // 电机为运动状态
  tim_count=__HAL_TIM_GET_COUNTER(&htim8);
  __HAL_TIM_SET_COMPARE(&htim8,TIM_CHANNEL_1,tim_count+srd.step_delay); // 设置定时器比较值
  TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_ENABLE);// 使能定时器通道 
  STEPMOTOR_OUTPUT_ENABLE();
}


/**
  * 函数功能: 定时器中断服务函数
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 实现加减速过程
  */
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断处理
{ 
  __IO uint32_t tim_count=0;
  __IO uint32_t tmp = 0;
  // 保存新(下)一个延时周期
  uint16_t new_step_delay=0;
  // 加速过程中最后一次延时(脉冲周期).
  __IO static uint16_t last_accel_delay=0;
  // 总移动步数计数器
  __IO static uint32_t step_count = 0;
  // 记录new_step_delay中的余数,提高下一步计算的精度
  __IO static int32_t rest = 0;
  //定时器使用翻转模式,需要进入两次中断才输出一个完整脉冲
  __IO static uint8_t i=0;
  
  if(__HAL_TIM_GET_IT_SOURCE(&htim8, TIM_IT_CC1) !=RESET)
  {
    // 清楚定时器中断
    __HAL_TIM_CLEAR_IT(&htim8, TIM_IT_CC1);
    
    // 设置比较值
    tim_count=__HAL_TIM_GET_COUNTER(&htim8);
    tmp = tim_count+srd.step_delay;
    __HAL_TIM_SET_COMPARE(&htim8,TIM_CHANNEL_1,tmp);

    i++;     // 定时器中断次数计数值
    if(i==2) // 2次,说明已经输出一个完整脉冲
    {
      i=0;   // 清零定时器中断次数计数值
      switch(srd.run_state) // 加减速曲线阶段
      {
        case STOP:
          step_count = 0;  // 清零步数计数器
          rest = 0;        // 清零余值
          // 关闭通道
          TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_DISABLE);        
          __HAL_TIM_CLEAR_FLAG(&htim8, TIM_FLAG_CC1);
          STEPMOTOR_OUTPUT_DISABLE();
          MotionStatus = 0;  //  电机为停止状态     
          break;

        case ACCEL:
          step_count++;      // 步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 绝对位置加1
          }
          else
          {
            step_position--; // 绝对位置减1
          }
          srd.accel_count++; // 加速计数值加1
          //计算新(下)一步脉冲周期(时间间隔)
          new_step_delay = srd.step_delay - (((2 *srd.step_delay) + rest)/(4 * srd.accel_count + 1));
          // 计算余数,下次计算补上余数,减少误差
          rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);
          if(step_count >= srd.decel_start)// 检查是够应该开始减速
          {
            srd.accel_count = srd.decel_val; // 加速计数值为减速阶段计数值的初始值
            srd.run_state = DECEL;           // 下个脉冲进入减速阶段
          }
          else if(new_step_delay <= srd.min_delay) // 检查是否到达期望的最大速度
          {
            last_accel_delay = new_step_delay; // 保存加速过程中最后一次延时(脉冲周期)
            new_step_delay = srd.min_delay;    // 使用min_delay(对应最大速度speed)
            rest = 0;                          // 清零余值
            srd.run_state = RUN;               // 设置为匀速运行状态
          }
          break;

        case RUN:
          step_count++;  // 步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 绝对位置加1
          }
          else
          {
            step_position--; // 绝对位置减1
          }
          new_step_delay = srd.min_delay;     // 使用min_delay(对应最大速度speed)
          if(step_count >= srd.decel_start)   // 需要开始减速
          {
            srd.accel_count = srd.decel_val;  // 减速步数做为加速计数值
            new_step_delay = last_accel_delay;// 加阶段最后的延时做为减速阶段的起始延时(脉冲周期)
            srd.run_state = DECEL;            // 状态改变为减速
          }
          break;

        case DECEL:
          step_count++;  // 步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 绝对位置加1
          }
          else
          {
            step_position--; // 绝对位置减1
          }
          srd.accel_count++;
          //计算新(下)一步脉冲周期(时间间隔)
          new_step_delay = srd.step_delay - (((2 * srd.step_delay) + rest)/(4 * srd.accel_count + 1)); 
          // 计算余数,下次计算补上余数,减少误差
          rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);
          //检查是否为最后一步
          if(srd.accel_count >= 0)
          {
            srd.run_state = STOP;
          }
          break;
      }      
      srd.step_delay = new_step_delay; // 为下个(新的)延时(脉冲周期)赋值
    }
  }
}

相对位置运动:运动给定的步数:void STEPMOTOR_AxisMoveRel( int32_t step, uint32_t accel, uint32_t decel, uint32_t speed);

定时器中断回调函数:void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);

电机控制部分T_Speed.c

typedef struct {
  __IO uint8_t  run_state ;  // 电机旋转状态
  __IO uint8_t  dir ;        // 电机旋转方向
  __IO int32_t  step_delay;  // 下个脉冲周期(时间间隔),启动时为加速度
  __IO uint32_t decel_start; // 启动减速位置
  __IO int32_t  decel_val;   // 减速阶段步数
  __IO int32_t  min_delay;   // 最小脉冲周期(最大速度,即匀速段速度)
  __IO int32_t  accel_count; // 加减速阶段计数值
}speedRampData;

#define STEPMOTOR_TIMx_IRQHandler  			  TIM8_CC_IRQHandler

#define FALSE                                 0
#define TRUE                                  1
#define CW                                    0 // 顺时针
#define CCW                                   1 // 逆时针

#define STOP                                  0 // 加减速曲线状态:停止
#define ACCEL                                 1 // 加减速曲线状态:加速阶段
#define DECEL                                 2 // 加减速曲线状态:减速阶段
#define RUN                                   3 // 加减速曲线状态:匀速阶段
#define T1_FREQ                               (SystemCoreClock/(5+1)) // 频率ft值
#define FSPR                                  200         //步进电机单圈步数
#define MICRO_STEP                            32          // 步进电机驱动器细分数
#define SPR                                   (FSPR*MICRO_STEP)   // 旋转一圈需要的脉冲数

// 数学常数
#define ALPHA                                 ((float)(2*3.14159/SPR))       // α= 2*pi/spr
#define A_T_x10                               ((float)(10*ALPHA*T1_FREQ))
#define T1_FREQ_148                           ((float)((T1_FREQ*0.676)/10)) // 0.676为误差修正值
#define A_SQ                                  ((float)(2*100000*ALPHA)) 
#define A_x200                                ((float)(200*ALPHA))

定义电机控制枚举,电机方向,电机状态,加减速状态,T形加减速算法

  • 主函数部分main.c
// 速度最大值由驱动器和电机决定,有些最大是1800,有些可以达到4000
__IO uint32_t set_speed  = 3000;         // 速度 单位为0.05rad/sec
// 加速度和减速度选取一般根据实际需要,值越大速度变化越快,加减速阶段比较抖动
// 所以加速度和减速度值一般是在实际应用中多尝试出来的结果
__IO uint32_t step_accel = 10;         // 加速度 单位为0.025rad/sec^2
__IO uint32_t step_decel = 10;         // 减速度 单位为0.025rad/sec^2

int main(void)
{
   /* 确定定时器 */
  HAL_TIM_Base_Start(&htim8);
  /* 使能中断 关闭比较输出*/
  HAL_TIM_OC_Start_IT(&htim8,TIM_CHANNEL_1);
  TIM_CCxChannelCmd(TIM8,TIM_CHANNEL_1,TIM_CCx_DISABLE);
    
while (1)
  {
	dir_val=(i++ % 2) ? CW : CCW; //方向判断
		
		if(dir_val)
		{
			STEPMOTOR_AxisMoveRel(6400*80, step_accel, step_decel, set_speed);//相对位置运动
		}
		else 
		{
			STEPMOTOR_AxisMoveRel(-6400*80, step_accel, step_decel, set_speed);//相对位置运动
		}
		
		HAL_Delay(5000);//要等旋转再反向旋转
	
	} 
}
5.4.2 S型加减速
  • 什么是S型加减速

在加减速的变化过程中速度曲线呈现一个英文字母“S”形的,我们称之为S形加减速算法。

在这里插入图片描述

  • 梯形加减速与S形加减速差别

S形加减速在启动停止以及高速运动时的速度变化的比较慢,导致冲击力噪音就很小。所以更适用于精密的工件搬运与建造。

在这里插入图片描述

  • S形加减速模型分析

S形加减速分为五段形和七段形:

5段式算法特点:算法简单、具有实时性和高精度的加减速控制算法,非常适合资源紧凑的小型嵌入式系统

7段式算法特点:具有平稳、精度高的特点,但该算法的参数复杂,大大降低到了工作效率且对硬件的要求较高

在这里插入图片描述

  • 七段式S形加减速模型分析

T1:加加速阶段

T2:匀加速阶段

T3:匀速阶段

T4:加减速阶段

T5:匀减速阶段

T6:减减速阶段

  • 五段式S形加减速模型分析

T1:加加速阶段

T2:减加速阶段

T3:匀速阶段

T4:加减速阶段

T5:减减速阶段

加加速度的开始点和结束点为零,加速度的斜率是相同的,所以T1=T2,T4=T5;

加速度的变化率(J)相等只是方向相反;

加速段位移和减速段位移算法一样,只是速度排序是倒序

  • S形加减速的原理分析

    • 需用户设置的已知量:

    步进的总步数:使用step表示、

    加速度段时间:使用ts1表示

    减速度段时间:使用ts2表示

    最大速度:电机匀速时的速度值,V表示

    • 需求解的未知量:

    加加速阶段步数:使用n1表示

    减加速阶段步数:使用n2表示

    下次脉冲周期:使用Cn表示

    在这里插入图片描述

  • S形加减速的算法实现

    加加速阶段步数求解

    在这里插入图片描述

    已知条件:T1 = T2

    对速度的积分面积就为位移,所以有:

    (1)

    在这里插入图片描述

    由于速度我们不知道所以要先求取速度,对加速度a积分则得速度的变化量,如下:

    (2)

    在这里插入图片描述

    a为加速度,加速度a从0变化到最大值,有如下:(其中J是加加速度(jerk),即加速度的变化率也就是斜率)因为这里为加加速度段,对应的时间t也是在一定范围内(t=[0,t1])

在这里插入图片描述

加加速阶段步数求解

当加速度a随着时间变化到最大值时,速度V = Vm,由于初速度为0,中点速度即为末速度的一半,即:

在这里插入图片描述

将Vm带入速度变化量公式:

在这里插入图片描述

将求得的加速度变化率J1代入位移公式,即可求出加加速段的位移:

在这里插入图片描述

即:

在这里插入图片描述

最终速度公式:

在这里插入图片描述

减加速阶段步数求解

在这里插入图片描述

要获取减加速度段的位移,同样要知道速度和加速度,而减加速阶段的加加速度J是和加加速阶段是相反的,即为-J,且t2-t1为减加速段所用时间,则有(t=[t1,t2])。设减加速阶段加速度为a2:

在这里插入图片描述

速度的变化量公式:

在这里插入图片描述

加速段的总位移为:
在这里插入图片描述

确定下一个脉冲周期

首先需将设定速度单位rpm转换为step/s,这样既可表示1秒内可步进多少步数;

速度v的倒数 1/V 单位就为s/step(每一步对应的时间)直接对应要输出脉冲的周期。

我们要如何转换?

在这里插入图片描述

所以下一个变化的脉冲的周期的Cn是多少?
在这里插入图片描述

确定下一个脉冲周期Tn

一个脉冲周期Tn(Tn = Cn / Ft),所以就有:

在这里插入图片描述

想要知道Cn,其中ft已知为定时器计数频率,需要求出速度Vn才可,在整个加速/减速过程中速度都是不一样的,所以需要申请一个BUF,存取每一步的速度值,即速度表。

在这里插入图片描述

其中J1我们已知,需要知道时间t,我们可以由位移公式,求出第一步的时间t:

在这里插入图片描述

当S = 1时,此时t1为第一步的时间我们知道第一步所需的时间t1,通过t1计算出下一步的速度V,速度的倒数1/V,就是第二步脉冲的周期… …

5.4.2.1 代码部分

定时器部分

void MX_TIM8_Init(void)
{

  /* USER CODE BEGIN TIM8_Init 0 */

  /* USER CODE END TIM8_Init 0 */

  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM8_Init 1 */

  /* USER CODE END TIM8_Init 1 */
  htim8.Instance = TIM8;
  htim8.Init.Prescaler = TIM_PRESCALER-1; //预分频值
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
  htim8.Init.Period = TIM_PERIOD-1;//重装载值
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim8.Init.RepetitionCounter = 0;
  htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_OC_Init(&htim8) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TOGGLE;//反转模式
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_OC_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM8_Init 2 */
	/* 确定定时器 */
	HAL_TIM_Base_Start(&htim8);
  /* USER CODE END TIM8_Init 2 */
  HAL_TIM_MspPostInit(&htim8);

}

tim.h部分

#define HIGH 1		//高电平
#define LOW  0		//低电平

#define ON  0			//开
#define OFF !0			//关

#define CLOCKWISE 			1//顺时针
#define ANTI_CLOCKWISE	0//逆时针

//输出比较模式周期设置为0xFFFF
#define TIM_PERIOD                   0xFFFF
#define TIM_PRESCALER                168-1

电机控制算法实现部分S_Speed.c

/*算法相关结构体变量定义*/
SpeedCalc_TypeDef Speed ;
Stepper_Typedef Stepper;

uint8_t print_flag=0;

//参数计算
void CalcSpeed(int32_t Vo, int32_t Vt, float T)
{
	
  uint8_t Is_Dec = FALSE;     
  int32_t i = 0;
  int32_t Vm =0;              // 中间点速度
  float K = 0;             // 加加速度
  float Ti = 0;               // 时间间隔 dt
  float Sumt = 0;             // 时间累加量
  float DeltaV = 0;           // 速度的增量dv  
	
	/***************************************************************************/
	/*判断初速度和末速度的关系,来决定加减速*/
  if(Vo > Vt )
	{                               
    Is_Dec = TRUE;
    Speed.Vo = CONVER(Vt);  
    Speed.Vt = CONVER(Vo); 
  }
  else
  {
    Is_Dec = FALSE;
    Speed.Vo = CONVER(Vo);    
    Speed.Vt = CONVER(Vt);    
  }
	/***************************************************************************/
	/*计算初始参数*/
	T = T / 2;						//加加速段的时间(加速度斜率>0的时间)
	
  Vm = (Speed.Vo + Speed.Vt) / 2;	//计算中点的速度
	
  K = fabs(( 2 * ((Vm) - (Speed.Vo)) ) / pow((T),2));// 根据中点速度计算加加速度
		
  Speed.INC_AccelTotalStep = (int32_t)((Speed.Vo * T)+(((K) * pow((T), 3)) / 6));// 加加速需要的步数
	
  Speed.Dec_AccelTotalStep = (int32_t)(((Speed.Vt + Speed.Vo) * T - Speed.INC_AccelTotalStep));   // 减加速需要的步数 S = Vt * Time - S1
  
	/***************************************************************************/
	/*计算共需要的步数,并校检内存大小,申请内存空间存放速度表*/
  Speed.AccelTotalStep = Speed.Dec_AccelTotalStep + Speed.INC_AccelTotalStep;              // 加速需要的步数 
  if( Speed.AccelTotalStep  % 2 != 0)     // 由于浮点型数据转换成整形数据带来了误差,所以这里加1
    Speed.AccelTotalStep  += 1;
	
	/*判断内存长度*/
	if(FORM_LEN<Speed.AccelTotalStep)
	{
		printf("FORM_LEN 缓存长度不足\r\n,请将 FORM_LEN 修改为 %d \r\n",Speed.AccelTotalStep);
		return ;
	}

	/***************************************************************************/
	/* 计算第一步的时间 */
		
	/*根据第一步的时间计算,第一步的速度和脉冲时间间隔*/
	/*根据位移为0的时候的情况,计算时间的关系式 ->  根据位移和时间的公式S = 1/2 * K * Ti^3  可得 Ti=6 * 1 / K开1/3次方 */
  Ti = pow((6.0f * 1.0f / K),(1 / 3.0f) ); //开方求解 Ti 时间常数
  Sumt += Ti;//累计时间常数
	/*根据V=1/2*K*T^2,可以计算第一步的速度*/
  DeltaV = 0.5f * K * pow(Sumt,2);
	/*在初始速度的基础上增加速度*/
  Speed.Form[0] = Speed.Vo + DeltaV;
  
	/***************************************************************************/
	/*最小速度限幅*/
  if( Speed.Form[0] <= MIN_SPEED )//以当前定时器频率所能达到的最低速度
    Speed.Form[0] = MIN_SPEED;
	
  /***************************************************************************/
	/*计算S形速度表*/
  for(i = 1; i < Speed.AccelTotalStep; i++)
  {
	
		/*根据时间周期与频率成反比的关系,可以计算出Ti,在这里每次计算上一步时间,用于积累到当前时间*/
		Ti = 1.0f / Speed.Form[i-1];   
    /* 加加速度计算 */
    if( i < Speed.INC_AccelTotalStep)
    {
			/*累积时间*/
      Sumt += Ti;
			/*速度的变化量 dV = 1/2 * K * Ti^2 */
      DeltaV = 0.5f * K * pow(Sumt,2);
			/*根据初始速度和变化量求得速度表*/
      Speed.Form[i] = Speed.Vo + DeltaV;  
			/*为了保证在最后一步可以使得时间严谨的与预期计算的时间一致,在最后一步进行处理*/
      if(i == Speed.INC_AccelTotalStep - 1)
        Sumt  = fabs(Sumt - T );
    }
    /* 减加速度计算 */
    else
    {
			/*时间累积*/
      Sumt += Ti;                                       
			/*计算速度*/
      DeltaV = 0.5f * K * pow(fabs( T - Sumt),2); 
      Speed.Form[i] = Speed.Vt - DeltaV;          
    }
  } 
	/***************************************************************************/
	/*减速运动,倒序排列*/
  if(Is_Dec == TRUE)
  {
    float tmp_Speed = 0;  
    /* 倒序排序 */
    for(i = 0; i< (Speed.AccelTotalStep / 2); i++)
    {
      tmp_Speed = Speed.Form[i];
      Speed.Form[i] = Speed.Form[Speed.AccelTotalStep-1 - i];
      Speed.Form[Speed.AccelTotalStep-1 - i] = tmp_Speed;
    }
  }
}


/**
  * @brief  速度决策
	*	@note 	在中断中使用,每进一次中断,决策一次
  * @retval 无
  */
void speed_decision(void)
{
	/*计数临时变量*/
  float temp_p = 0;
	/*脉冲计数*/
  static uint8_t i = 0;  	
  
	if(__HAL_TIM_GET_IT_SOURCE(&htim8, TIM_IT_CC1) !=RESET)
	{
		/*清除定时器中断*/
		__HAL_TIM_CLEAR_IT(&htim8, TIM_IT_CC1);
		
		/******************************************************************/
		/*两次为一个脉冲周期*/
		i++; 
    if(i == 2)
    {
			/*脉冲周期完整后清零*/
      i = 0;   
			/*判断当前的状态,*/
      if(Stepper.status == ACCEL || Stepper.status == DECEL)
      {
				/*步数位置索引递增*/
        Stepper.pos++;
        if(Stepper.pos  < Speed.AccelTotalStep )
        { 
					/*获取每一步的定时器计数值*/
          temp_p = T1_FREQ / Speed.Form[Stepper.pos];
          if((temp_p / 2) >= 0xFFFF)
            {Stepper.pluse_time = 0xFFFF;}
          else
            {Stepper.pluse_time = (uint16_t)roundf((temp_p / 2));}
        }
        else
        {
					/*加速部分结束后接下来就是匀速状态或者停止状态*/
          if(Stepper.status == ACCEL)   
					{
					  Stepper.status = AVESPEED;
					}          
          else
          {
						/*停止状态,清空速度表并且关闭通道*/
            Stepper.status = STOP; 
						memset(Speed.Form,0,sizeof(float)*FORM_LEN);
            TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_DISABLE);// 使能定时器通道 
            
          }
        }
      }
    }
		/**********************************************************************/
		// 获取当前计数器数值
		uint32_t tim_count=__HAL_TIM_GET_COUNTER(&htim8);
		/*计算下一次时间*/
		uint32_t tmp = tim_count+Stepper.pluse_time;
		/*设置比较值*/
		__HAL_TIM_SET_COMPARE(&htim8,TIM_CHANNEL_1,tmp);
		
	}
}


/**
  * @brief  初始化状态并且设置第一步的速度
  * @param  无
	* @param  无
	*	@note 		无
  * @retval 无
  */
void stepper_start_run()
{

	/*初始化结构体*/
	memset(&Stepper,0,sizeof(Stepper_Typedef));
	/*初始电机状态*/
	Stepper.status=ACCEL;
	/*初始电机位置*/
	Stepper.pos=0;
	
	/*计算第一次脉冲间隔*/
  if(Speed.Form[0] == 0)	//排除分母为0的情况
    Stepper.pluse_time = 0xFFFF;
  else										//分母不为0的情况
    Stepper.pluse_time  = (uint32_t)(T1_FREQ/Speed.Form[0]);
	
	/*获取当前计数值*/
	uint32_t temp=__HAL_TIM_GET_COUNTER(&htim8);
	/*在当前计数值基础上设置定时器比较值*/
	__HAL_TIM_SET_COMPARE(&htim8,TIM_CHANNEL_1,temp +Stepper.pluse_time); 
	/*开启中断输出比较*/
	HAL_TIM_OC_Start_IT(&htim8,TIM_CHANNEL_1);
	/*使能定时器通道*/
	TIM_CCxChannelCmd(TIM8, TIM_CHANNEL_1, TIM_CCx_ENABLE);
}


/*! \brief 给固定的时间和速度,使得步进电机在固定时间内达到目标速度
 *  \param start_speed   	初始速度
 *  \param end_speed  		结束速度
 *  \param time  					时间
 */
void stepper_move_S(int start_speed,int end_speed,float time)
{
	/*计算参数*/
	CalcSpeed(start_speed,end_speed,time);
	/*开始旋转*/
	stepper_start_run();
}

//定时器中断服务函数
void TIM8_CC_IRQHandler(void)
{ 
	HAL_TIM_IRQHandler(&htim8);
	/*速度状态决策*/
	speed_decision();
}

参数计算函数:void CalcSpeed(int32_t Vo, int32_t Vt, float T)

速度决策函数:void speed_decision(void)

初始化状态并且设置第一步的速度:void stepper_start_run()

电机控制函数:void stepper_move_S(int start_speed,int end_speed,float time);

定时器中断服务函数:void TI;M8_CC_IRQHandler(void)

电机控制算法实现部分S_Speed.h

#define CONVER(speed)  ((speed) * FSPR * MICRO_STEP / 60)  // 根据电机转速(r/min),计算电机步速(step/s)

#define FORM_LEN 	   1000

typedef struct {
	uint8_t 	status;			/*状态*/
	uint8_t 	dir;				/*方向*/
	uint32_t 	pos;				/*位置*/
	uint32_t  pluse_time; /*脉冲时间*/
}Stepper_Typedef;

/*S加减速所用到的参数*/
typedef struct {
  int32_t   Vo;                	/*初始速度*/
  int32_t   Vt;               	/*末速度*/
  int32_t   AccelTotalStep;   	/*加速总步数*/  
  int32_t   INC_AccelTotalStep;	/*加加速度步数*/
  int32_t   Dec_AccelTotalStep;	/*减加速度步数*/
  float     Form[FORM_LEN];     /*S加减速 速度表*/ 
}SpeedCalc_TypeDef;

extern SpeedCalc_TypeDef Speed ;

#define CW                		0 // 顺时针
#define CCW               		1 // 逆时针

/*电机速度决策中的四个状态*/
#define ACCEL                 1   //  加速状态
#define AVESPEED              2   //  匀速状态
#define DECEL                 3   //  减速状态
#define STOP                  0   //  停止状态
																												  

/*频率相关参数*/
//定时器实际时钟频率为:168MHz/(TIM_PRESCALER+1)
//其中 高级定时器的 频率为168MHz,其他定时器为84MHz
//168/(168)=1Mhz
//具体需要的频率可以自己计算
#define TIM_PRESCALER         168-1 
#define T1_FREQ               (SystemCoreClock/(TIM_PRESCALER+1)) // 频率ft值


/*电机单圈参数*/
#define STEP_ANGLE						1.8									//步进电机的步距角 单位:度
#define FSPR              		(360.0f/1.8f)         //步进电机的一圈所需脉冲数
			
#define MICRO_STEP        		32          				//细分器细分数 
#define SPR               		(FSPR*MICRO_STEP)   //细分后一圈所需脉冲数

#define CONVER(speed)  ((speed) * FSPR * MICRO_STEP / 60)  // 根据电机转速(r/min),计算电机步速(step/s)

#define TRUE                  1
#define FALSE                 0

#define MIN_SPEED                              (T1_FREQ / (65535.0f))// 最低频率/速度

设置电机控制枚举,电机S加减速参数,电机方向,电机状态,电机单圈参数

  • 主函数部分main.c
while (1)
  {
  
	  if(Key_Scan(KEY2_GPIO_Port,KEY2_Pin) == KEY_ON)
	{
		if(print_flag)
		{
			/*步进电机加速部分*/
			stepper_move_S(1,100,0.1f);
			print_flag=0; 
		}
		
	}
	if( Stepper.status == AVESPEED)
		{
			/*步进电机减速部分*/
			stepper_move_S(100,1,0.1f);
			Stepper.status = DECEL;    //减速状态
		}
	  
  }
  • 31
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值