定时器做编码器
STM32F401的所有定时器都可以作为编码器使用(STM32F401在某宝20RMB以内买到的,晶振不同,不知道怎么操作的看我上一篇博文,里面有介绍怎么移植,经过本人验证,目前没有任何问题)
下面步入正题:
TIMER都可以作为编码器采集通道,但是需要十分注意的是:
定时器只有通道1和通道2可以作为编码器使用,当采用编码器的2个通道后,剩余的2个通道一般而言就没什么用了,当然,对于高手而言,或许有他们的办法。
我使用的方式,以TIM4的通道1(CH1)和通道2(CH2)作为电机编码器采集通道,以TIM2作为定时中断,TIM3作为PWM信号控制,实现了编码器的数据采集。可以参考这篇博文的做法
硬件电机驱动采用了某宝3.5RMB的TB6612电机驱动,这里需要注意的是STBY引脚一定要接到3.3v-5v, 如果悬空的话,是不会输出的,AIN1和AIN2引脚为方向控制,PWM引脚接PWM输出信号,AO1和AO2引脚是电机驱动引脚,VM是接12V的,VCC是接3.3v-5v的, 这个千万不能接错。当然,TB6612模块的胆电容其实我觉得不怎么好用,被我弄爆炸了2个,当然这个电容的目的是保护电路不受上电掉电时的电流冲击,爆了之后依然可以用,只要不会短路就行,TB6612模块还是很皮实的。
方向控制表:
tb6612连线图:
上面这个图上的电机在某宝某小车买的,太贵了,如果不是RMB玩家建议买其他的。
重点:
TIM4作为编码器通道后,PB6和PB7作为CH1和CH2,由于是复用的,在配置GPIO时候一定要配置为复用的方式。同时,在采集数据的时候一定要注意TIM4->CNT的数据格式。
实际代码:
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "oled.h"
#include "bmp.h"
#include "timer32bit.h"
#include "iwdg.h"
#include "motor.h"
/*====================================================================================================================*/
/*====================================================================================================================*/
/*====================================================================================================================*/
/*****************************************************程序改动说明*****************************************************/
//根据采用不同晶振 STM32F401 改动的地方有:
//OptionForTarget xtal(改为和实际晶振大小一致)
//delay.c 121、187、206行(降低计时误差 不改动定时误差为5% 改动后延时为偶数无误差,为奇数误差为1/n *100%)
//system_stm32f4xx.c 317行(根据system_stm32f4xx.c 137-181行的说明改动)
//stm32f4xx.h 123行(将HSE晶振数值更新为与实际一样)
/*====================================================================================================================*/
/*====================================================================================================================*/
/*====================================================================================================================*/
double zhankongbi = 0.6;
u16 time2_count_threshold = 0;
u8 Direction = 0;
int main(void)
{
delay_init(84); //初始化延时函数
LED_Init(); //初始化LED端口
KEY_Init();
OLED_Init();
OLED_ShowPicture(0,0,128,64,BMP4,1);//LOGO
LED_Shine(300,3);
delay_ms(1000);
ShowFrequence();//显示时钟频率
delay_ms(1000);
OLED_Clear();
TIM2_Int_Init(10000-1,840-1);//周期 10^(5-6) s
TIM3_Int_Init(1000-1,84-1);//计时周期 (PWM周期)1^(3-6) s
Encoder_Init_TIM4();
Motor_Init();
Direction_Front;
/**下面是通过直接操作库函数的方式实现IO控制**/
while(1)
{
if(KEY_Scan(0)==KEY0_PRES)
{
Direction = Direction==1?0:1;
if(Direction==0)
{
Direction_Front;
}
else
{
Direction_Back;
}
}
time2_count_threshold = (u16)(1000*zhankongbi);//调整PWM比较值
//PWM观察 因为PC13没有PWM功能 采用软PWM
if(TIM3->CNT < time2_count_threshold)
{
MotorPWM = 1;
LED0 = 0;
}
else
{
MotorPWM = 0;
LED0 = 1;
}
}
}
motor.c
#include "motor.h"
void Motor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOC时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;//LED对应IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
GPIO_SetBits(GPIOA,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10);//关闭PWM
Direction_Front;
}
void Encoder_Init_TIM4(void)
{
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// GPIO口初始化
GPIO_InitTypeDef GPIO_InitStructure; // GPIO口的输入模式配置很重要,不正确的话会读不到数据
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_TIM4);
// 定时器编码器模式初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_DeInit(TIM4);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 这里可以自己设置,或使用默认值
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); // 这里配置了编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
//TIM_ICInitStructure.TIM_ICFilter = 10; // 这里可以自己设置,或使用默认值
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_SetCounter(TIM4, 0);
TIM_Cmd(TIM4, ENABLE);
}
motor.h
#ifndef _MOTOR_H
#define _MOTOR_H
#include "sys.h"
#define MotorPWM PAout(8)
#define Motor_AIN1 PAout(9)
#define Motor_AIN2 PAout(10)
#define Direction_Front {Motor_AIN1 = 0 ; Motor_AIN2 = 1;}
#define Direction_Back {Motor_AIN1 = 1 ; Motor_AIN2 = 0;}
void Motor_Init(void);
void Encoder_Init_TIM4(void);//32位定时器
#endif
timer32bit.c
#include "timer32bit.h"
#include "led.h"
#include "oled.h"
float counter = 0;//Time2 计数值
u32 times = 0;//Time3 时间
int Encoder_TIM = 0;
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向下计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_Cmd(TIM3,ENABLE); //使能定时器3
}
//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断
{
extern double zhankongbi;
zhankongbi += 0.01;
if(zhankongbi >= 1)
zhankongbi = 0.2;
Encoder_TIM = (int)((int16_t)(TIM4->CNT));
TIM4->CNT=0;
Encoder_TIM *= 10;
Encoder_TIM = (int)(Encoder_TIM /26);//每分钟转速 Encoder_TIM*10/1556*60 r/min
counter += (float)((float)(Encoder_TIM)/600);//100ms 圈数
OLED_ShowString(5,0,"Zhuan Shu :",8,1);
OLED_ShowNum(25,10,(u32)(counter),10,8,1);
OLED_ShowString(92,10,"r",8,1);
OLED_ShowString(5,20,"Shu Du :",8,1);
OLED_ShowNum(25+6*5,30,Encoder_TIM,5,8,1);
OLED_ShowString(92,30,"r/min",8,1);
OLED_ShowString(5,40,"ZhanKongBi:",8,1);
OLED_ShowNum(25+8*6,50,zhankongbi*100,2,8,1);
OLED_ShowString(92,50,"%",8,1);
OLED_Refresh();
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
}
void TIM2_Int_Init(u32 arr,u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = arr; //0xffffffff 可以计数到 2^32 次方
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //84分频 us 级别计数
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_Cmd(TIM2,ENABLE); //使能定时器2
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器3更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
timer32bit.h
#ifndef __TIMER32BIT__
#define __TIMER32BIT__
#include "sys.h"
void TIM2_Int_Init(u32 arr,u16 psc);//32位定时器
void TIM3_Int_Init(u16 arr,u16 psc);//16位定时器
#endif
效果图
完整工程链接
提取码:vdh2