本文使用 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等。
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » STM32—旋转编码器控制直流电机(标准库)
发表评论 取消回复