自学内容网 自学内容网

STM32—旋转编码器控制直流电机(标准库)

本文使用 KY-040旋转编码器 通过TC1508A电机驱动模块来控制直流电机正转和反转(Speed:0-100),代码部分基于标准库,使用定时器输出比较两个通道来控制PWM输出。

一、KY-040旋转编码器

下图为KY-040旋转编码器,它有5个引脚:CLK、DT、SW、VCC、GND

作者是这么理解的:CLK是时钟,DT是数据。这两个引脚输出的时序反映了旋转编码器所产生的中断信号。SW是编码器按键,设置GPIO为上拉输入,当编码器被按下时,SW输出低电平。VCC正极,GND负极。

CLK和DT相当于编码器时序的A相和B相,如上图,设置这两个引脚的GPIO为下拉输入,上升沿触发中断。当正时针旋转编码器时,红色笔迹显示,在CLK(TI1)触发中断时,DT(TI2)为低电平。绿色笔迹显示,当DT(TI2)触发中断时,CLK(TI1)为高电平。 

CLK引脚产生的中断处理函数如下:

DT引脚产生的中断处理函数如下: 

按下编码器可以来设置正反转,这里我使用了LED灯的亮灭来反映正反转的状态,正亮,反灭。每按下一次,灯的状态翻转。再通过检测灯的状态来控制正反转(这一步按自己设计代码来,就相当于按键控制LED灯。我这里将这段代码写在main函数里,因为需要初始化LED

 我的代码不够简练,作为小白什么都不懂,只知道到处碰壁。

Encoder.c:

#include "stm32f10x.h"                  // Device header
#include "Encoder.h" 

int16_t Encoder_Count;//全局变量,用于计数旋转编码器的增量值

/**
  * 函    数:旋转编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(Encoder_GPIO_CLK, ENABLE);    //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启AFIO的时钟,外部中断必须开启AFIO的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = Encoder_GPIO_Pin_CLK | Encoder_GPIO_Pin_DT | Encoder_GPIO_Pin_SW;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Encoder_GPIO_Port, &GPIO_InitStructure);//将PB0和PB1引脚初始化为上拉输入

/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
  GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚

/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure;//定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;//选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure);//将结构体变量交给EXTI_Init,配置EXTI外设

/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);//将结构体变量交给NVIC_Init,配置NVIC外设

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure);//将结构体变量交给NVIC_Init,配置NVIC外设
}

/**
  * 函    数:旋转编码器获取增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,旋转编码器的增量值
  */
int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
/*在这里,也可以直接返回Encoder_Count
  但这样就不是获取增量值的操作方法了
  也可以实现功能,只是思路不一样*/
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}



/**
  * 函    数:EXTI0外部中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)//判断是否是外部中断0号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
  if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 1)
{
if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 0)//下降沿触发中断,此时检测另一相电平,目的是判断旋转方向
{
Encoder_Count++;
        
}
    }
EXTI_ClearITPendingBit(EXTI_Line0);//清除外部中断0号线的中断标志位

}


}

///**
//  * 函    数:EXTI1外部中断函数
//  * 参    数:无
//  * 返 回 值:无
//  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
//  *           函数名为预留的指定名称,可以从启动文件复制
//  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
//  */
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)//判断是否是外部中断0号线触发的中断
{
    if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 1)
{
if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 0)//下降沿触发中断,此时检测另一相电平,目的是判断旋转方向
{
Encoder_Count--;//此方向定义为反转,计数变量自减
        
      }
    }

EXTI_ClearITPendingBit(EXTI_Line1);//清除外部中断0号线的中断标志位

}

}

Encoder.h:

#ifndef __ENCODER_H
#define __ENCODER_H

#define Encoder_GPIO_CLK      RCC_APB2Periph_GPIOD
#define Encoder_GPIO_Port     GPIOD
#define Encoder_GPIO_Pin_CLK  GPIO_Pin_0
#define Encoder_GPIO_Pin_DT   GPIO_Pin_1
#define Encoder_GPIO_Pin_SW   GPIO_Pin_2
#define Encoder_GPIO_Port_Source   GPIO_PortSourceGPIOD

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

二、TC1508电机驱动模块

  • IN1和IN2用来控制motor-A
  • IN3和IN4用来控制motor-B

 如下图,IN1输入PWM,配合IN2输入0,可实现直流电机正转;IN1输入0,配合IN2输入PWM,即可实现直流电机反转。PWM为调节速度所用。

于是在旋转编码器部分设置好后,再开始写PWM波形代码,PWM部分我使用的是定时器TIM3,TIM3有4个通道,我使用了 Channel1和Channel2 两个通道来分别输出PWM。

由于我的开发板上两个引脚PA6、PA7、PB4、PB5都被占用,所以使用TIM3完全重映像来设置PC6和PC7来输出IN1、IN2。

完全重映像代码

PWM设置两个通道的时候需要注意,先TIM_OC1Init,再GPIO_Init。如果顺序弄反,通道一和通道二无法对应PC6和PC7,我也不知道为什么。希望有大神在评论区解答一下,谢谢!

PWM_Init:

void PWM_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

TIM_InternalClockConfig(TIM3);

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;

TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC2Init(TIM3, &TIM_OCInitStruct);


GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);

TIM_Cmd(TIM3, ENABLE);

}
 

 然后连线,连线需要注意的是,TC1508A电机驱动模块的正负极容易接触不良。可以接完正负极,MOTORA连接电机后,通过一根线连接5v和IN1(或者IN2)测试电机是否正常启动。

主函数可以按自己想法来,作者这里的思路是:

1,在while之前,编码器初始化、PWM初始化。

2,while中,通过编码器转动产生中断获取速度的加减,通过按键中断翻转LED灯,再通过LED状态判断是正转还是反转。

3,如果是正转,通过TIM_SetCompare1(TIM3, Compare); 来设置速度,如果是反转,通过TIM_SetCompare2(TIM3, Compare); 来设置速度。

三、各模块代码部分

Encoder.c:

#include "stm32f10x.h"                  
#include "Encoder.h" 

int16_t Encoder_Count;//全局变量,用于计数旋转编码器的增量值


void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(Encoder_GPIO_CLK, ENABLE);//开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启AFIO的时钟,外部中断必须开启AFIO的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = Encoder_GPIO_Pin_CLK | Encoder_GPIO_Pin_DT | Encoder_GPIO_Pin_SW;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Encoder_GPIO_Port, &GPIO_InitStructure);//将PB0和PB1引脚初始化为上拉输入

/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
  GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚

/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure;//定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;//选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure);//将结构体变量交给EXTI_Init,配置EXTI外设

/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC为分组2
//即抢占优先级范围:0~3

/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);//将结构体变量交给NVIC_Init,配置NVIC外设

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure);//将结构体变量交给NVIC_Init,配置NVIC外设
}


int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
/*在这里,也可以直接返回Encoder_Count
  但这样就不是获取增量值的操作方法了
  也可以实现功能,只是思路不一样*/
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}


void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)//判断是否是外部中断0号线触发的中断
{

  if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 1)  // 再次判断引脚电平,以避免抖动,其实没必要
{
if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 0)
{
Encoder_Count++;
}
    }
EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位

}


}

void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断0号线触发的中断
{
    if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 1)
{
if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 0)
{
Encoder_Count--;//此方向定义为反转,计数变量自减  
      }
    }
    
EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位
}

}

Encoder.h:

#ifndef __ENCODER_H
#define __ENCODER_H

#define Encoder_GPIO_CLK      RCC_APB2Periph_GPIOD
#define Encoder_GPIO_Port     GPIOD
#define Encoder_GPIO_Pin_CLK  GPIO_Pin_0
#define Encoder_GPIO_Pin_DT   GPIO_Pin_1
#define Encoder_GPIO_Pin_SW   GPIO_Pin_2
#define Encoder_GPIO_Port_Source   GPIO_PortSourceGPIOD

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

PWM.c:

#include "stm32f10x.h"              
#include "PWM.h" 


void PWM_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

  GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

TIM_InternalClockConfig(TIM3);

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;

TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC2Init(TIM3, &TIM_OCInitStruct);


GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);

TIM_Cmd(TIM3, ENABLE);

}
 


void TIM3_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM3, Compare);//设置CCR1的值
}


void TIM3_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM3, Compare);//设置CCR1的值
}

PWM.h:

#ifndef __PWM_H
#define __PWM_H


void PWM_Init(void);
void TIM3_SetCompare1(uint16_t Compare);
void TIM3_SetCompare2(uint16_t Compare);


#endif

main.c: 部分代码,因为作者主函数代码太多,删减了一部分:OLED显示、串口显示......

#include "stm32f10x.h"                  // Device header
#include "LED.h"
#include "Encoder.h"
#include "PWM.h"


uint8_t i;          //定义for循环的变量

int16_t Num;//定义待被旋转编码器调节的变量
int16_t Speed;


int main(void)
{
  /*模块初始化*/
  LED_Init();
  PWM_Init();
  Encoder_Init();//旋转编码器初始化

while (1)
{
      
Num += Encoder_Get();//获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
        Speed = Num*10;
        if(Speed>100)
    {
      Speed=100;
    }
    else if(Speed<0)
    {
      Speed=0;
    }
    
    if (GPIO_ReadInputDataBit(Encoder_GPIO_Port,Encoder_GPIO_Pin_SW) == 0)//读PB1输入寄存器的状态,如果为1,则代表按键1按下
    {
      Delay_ms(10);//延时消抖
      while (GPIO_ReadInputDataBit(Encoder_GPIO_Port,Encoder_GPIO_Pin_SW) == 0);//等待按键松手
      Delay_ms(10);//延时消抖
      LED1_Turn();
      Speed=00;
      Num=0;
    }
    
    if((GPIO_ReadOutputDataBit(LED1_GPIO_Port, LED1_GPIO_Pin) == 1))
    {
      TIM3_SetCompare1(Speed);
      TIM3_SetCompare2(0);
    }
    else
    {
      TIM3_SetCompare1(0);
      TIM3_SetCompare2(Speed);
    }
    
  }
}

整体代码,需要根据自己的实际情况修改,比如不用重映像、不用LED等。


原文地址:https://blog.csdn.net/hjlkklk/article/details/142960567

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!