软硬件资源

  • STM32F103C8T6

  • 直流减速电机+霍尔编码器

  • HAL 库

这个本来挺简单的,但是因为我看错教程了(看的野火的电机教程),导致多花好几天时间,一度怀疑是编码器的脉冲信号出了问题,最后没想到还是芯片型号的问题。野火用的是 STM32F4 系列,其编码器代码不能直接用到 STM32F103C8T6 上,会出现编码器不计数、也无法读取计数方向的问题。

用编码器模式测速需要两个定时器:一个定时器设置为编码器模式,一个定时器周期性的计算转速。

我的项目有两个电机,因此需要三个定时器。完整代码如下,代码含义见注释。

编码器配置

encoder. h

#ifndef __ENCODER_H
#define __ENCODER_H

#include "stm32f1xx.h"

/**************引脚定义**************/
#define Encoder_E1A_PIN           GPIO_PIN_0  //电机 1 的 A 相编码值 
#define Encoder_E1B_PIN           GPIO_PIN_1  //电机 1 的 B 相编码值 
#define Encoder_E1A_PORT          GPIOA 
#define Encoder_E1B_PORT          GPIOA 
#define Encoder_E1A_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE() 
#define Encoder_E1B_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE() 

#define Encoder_E2A_PIN           GPIO_PIN_6  //电机 2 的A相编码值 
#define Encoder_E2B_PIN           GPIO_PIN_7  //电机 2 的B相编码值 
#define Encoder_E2A_PORT          GPIOA 
#define Encoder_E2B_PORT          GPIOA
#define Encoder_E2A_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE() 
#define Encoder_E2B_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE() 

/*************电机实际旋转方向*******/
extern __IO int8_t Motor_A_Direction;
extern __IO int8_t Motor_B_Direction;
#define MOTOR_FORWARD 0 //电机正转
#define MOTOR_REVERSE 1 //电机反转

/*************电机实际转速**********/
extern __IO float Motor_A_Speed;
extern __IO float Motor_B_Speed;

/********编码器对应定时器的计数值*****/
//当前时刻定时器计数值
extern __IO int32_t Motor_A_Count;
extern __IO int32_t Motor_B_Count;

/*************编码器定义************/
//定时器选择
#define ENCODER_A_TIM TIM2
#define ENCODER_B_TIM TIM3
#define ENCODER_A_TIMx 2
#define ENCODER_B_TIMx 3
#define ENCODER_A_TIM_CLK_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE()
#define ENCODER_B_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
//定时器溢出值
#define ENCODER_A_TIM_PERIOD 65535
#define ENCODER_B_TIM_PERIOD 65535
//定时器预分频值
#define ENCODER_A_TIM_PRESCALER 0
#define ENCODER_B_TIM_PRESCALER 0
//编码器倍频数
#define ENCODER_MODE TIM_ENCODERMODE_TI12//4倍频
//编码器输入捕获通道极性选择
#define ENCODER_A_IC1_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_A_IC2_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_B_IC1_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_B_IC2_POLARITY TIM_ICPOLARITY_RISING
//编码器物理分辨率
#define ENCODER_RESOLUTION 13.0
//直流减速电机减速比
#define REDUCTION_RATIO 30.0
//编码器时基单元
extern TIM_HandleTypeDef Encoder_A_TIM_Base;//电机1编码器
extern TIM_HandleTypeDef Encoder_B_TIM_Base;//电机2编码器

//编码器采样周期
#define Encoder_Period 5
//编码器采样频率
#define Encoder_Frequency 200.0
//编码器倍频数
#define EncoderMultiples 4.0

//行进速度
extern float Velocity_Motor_A;
extern float Velocity_Motor_B;

//圆周率
#define PI 3.14159265

//轮子直径(mm)
#define Diameter 67.0

//编码器初始化函数
void Encoder_Init(void);
//读取单位时间内的编码器的计数值
int Read_Encoder(uint8_t TIMX);

#endif

encoder. c

#include "encoder.h"
#include "stm32f1xx_hal_tim.h"

TIM_HandleTypeDef Encoder_A_TIM_Base;//电机1编码器
TIM_HandleTypeDef Encoder_B_TIM_Base;//电机2编码器
//电机实际旋转方向
__IO int8_t Motor_A_Direction = 0;
__IO int8_t Motor_B_Direction = 0;
//当前时刻定时器计数值
__IO int32_t Motor_A_Count = 0;
__IO int32_t Motor_B_Count = 0;
//电机实际转速
__IO float Motor_A_Speed = 0.0f;
__IO float Motor_B_Speed = 0.0f;
//行进速度
float Velocity_Motor_A = 0;
float Velocity_Motor_B = 0;

/**
  * @brief  编码器接口引脚初始化
  * @param  无
  * @retval 无
  */
void Encoder_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    //GPIO时钟
    Encoder_E1A_CLK_ENABLE();
    Encoder_E1B_CLK_ENABLE();
    Encoder_E2A_CLK_ENABLE();
    Encoder_E2B_CLK_ENABLE();

    /*********************初始化编码器输入引脚**********/
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;//输入
    GPIO_InitStructure.Pin = Encoder_E1A_PIN;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(Encoder_E1A_PORT,&GPIO_InitStructure);

    GPIO_InitStructure.Pin = Encoder_E1B_PIN;
    HAL_GPIO_Init(Encoder_E1B_PORT,&GPIO_InitStructure);

    GPIO_InitStructure.Pin = Encoder_E2A_PIN;
    HAL_GPIO_Init(Encoder_E2A_PORT,&GPIO_InitStructure);

    GPIO_InitStructure.Pin = Encoder_E2B_PIN;
    HAL_GPIO_Init(Encoder_E2B_PORT,&GPIO_InitStructure);
}

/**
 * @brief 定时器初始化
 * 
 */
void TIM_Encoder_Init(void){
    TIM_Encoder_InitTypeDef TIM_Encoder_InitStructure = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    //编码器时钟
    ENCODER_A_TIM_CLK_ENABLE();
    ENCODER_B_TIM_CLK_ENABLE();

    /********配置编码器对应定时器*******/
    Encoder_A_TIM_Base.Instance = ENCODER_A_TIM;
    Encoder_A_TIM_Base.Init.Prescaler = ENCODER_A_TIM_PRESCALER;
    Encoder_A_TIM_Base.Init.CounterMode = TIM_COUNTERMODE_UP;
    Encoder_A_TIM_Base.Init.Period = ENCODER_A_TIM_PERIOD;
    Encoder_A_TIM_Base.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    Encoder_A_TIM_Base.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

    Encoder_B_TIM_Base.Instance = ENCODER_B_TIM;
    Encoder_B_TIM_Base.Init.Prescaler = ENCODER_B_TIM_PRESCALER;
    Encoder_B_TIM_Base.Init.CounterMode = TIM_COUNTERMODE_UP;
    Encoder_B_TIM_Base.Init.Period = ENCODER_B_TIM_PERIOD;
    Encoder_B_TIM_Base.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    Encoder_B_TIM_Base.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

    /*********************配置编码器接口***************/
    //编码器倍频数
    TIM_Encoder_InitStructure.EncoderMode = ENCODER_MODE;
    //电机A对应编码器通道1(A相)设置
    TIM_Encoder_InitStructure.IC1Polarity = ENCODER_A_IC1_POLARITY;
    TIM_Encoder_InitStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    TIM_Encoder_InitStructure.IC1Prescaler = TIM_ICPSC_DIV1;
    TIM_Encoder_InitStructure.IC1Filter = 10;
    //电机A对应编码器通道2(B相)设置
    TIM_Encoder_InitStructure.IC2Polarity = ENCODER_A_IC2_POLARITY;
    TIM_Encoder_InitStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    TIM_Encoder_InitStructure.IC2Prescaler = TIM_ICPSC_DIV1;
    TIM_Encoder_InitStructure.IC2Filter = 10;
    //初始化电机A对应编码器接口
    HAL_TIM_Encoder_Init(&Encoder_A_TIM_Base,&TIM_Encoder_InitStructure);

    //电机B对应编码器通道1(A相)设置
    TIM_Encoder_InitStructure.IC1Polarity = ENCODER_B_IC1_POLARITY;
    //电机B对应编码器通道2(B相)设置
    TIM_Encoder_InitStructure.IC2Polarity = ENCODER_B_IC2_POLARITY;
    //初始化电机B对应编码器接口
    HAL_TIM_Encoder_Init(&Encoder_B_TIM_Base,&TIM_Encoder_InitStructure);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&Encoder_A_TIM_Base,&sMasterConfig);
    HAL_TIMEx_MasterConfigSynchronization(&Encoder_B_TIM_Base,&sMasterConfig);

    /********************使能编码器接口****************/
    HAL_TIM_Encoder_Start(&Encoder_A_TIM_Base,TIM_CHANNEL_ALL);
    HAL_TIM_Encoder_Start(&Encoder_B_TIM_Base,TIM_CHANNEL_ALL);
}

/**
 * @brief 初始化编码器
 * 
 */
void Encoder_Init(void){
    Encoder_GPIO_Init();
    TIM_Encoder_Init();
}

/**
 * @brief 读取单位时间内的编码器计数值
 * 
 * @param 编码器对应到的定时器
 * @return 编码器计数值
 */
int Read_Encoder(uint8_t TIMX)
{
    int Encoder_TIM;  
    switch(TIMX)
    {
        //电机A对应编码器
        case 2:  Encoder_TIM= (short)TIM2 -> CNT;  TIM2 -> CNT=0;break;
        //电机B对应编码器
        case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;
        default: Encoder_TIM=0;
    }
    return Encoder_TIM;
}

计算转速

计算转速的定时器我用的是 systick 的回调函数,这个函数会在 systick(滴答定时器)每次计数时调用一次。

/**
 * @brief systick 回调函数
 * 
 */
void HAL_SYSTICK_Callback(void){
  static uint16_t i = 0;
  i++;
  //周期获取电机旋转方向和速度
  if(i == Encoder_Period){
    //电机旋转方向
    Motor_A_Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&Encoder_A_TIM_Base);
    Motor_B_Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&Encoder_B_TIM_Base);

    //当前时刻定时器计数值
    Motor_A_Count = Read_Encoder(ENCODER_A_TIMx);
    Motor_B_Count = Read_Encoder(ENCODER_B_TIMx);

    //计算转速:M法 单位时间计数值 / 倍频数 / 减速比 / 编码器分辨率(将单位转换为 /s)
    Motor_A_Speed = Motor_A_Count * Encoder_Frequency / EncoderMultiples / REDUCTION_RATIO / ENCODER_RESOLUTION;
    Motor_B_Speed = Motor_B_Count * Encoder_Frequency / EncoderMultiples / REDUCTION_RATIO / ENCODER_RESOLUTION;

    //换算为行进速度:转速*轮子周长(mm/s)
    Velocity_Motor_A = Motor_A_Speed * PI * Diameter;
    Velocity_Motor_B = Motor_B_Speed * PI * Diameter;

    i = 0;
  }
}
最后修改:2023 年 09 月 28 日
如果觉得我的文章对你有用,请随意赞赏