一.电机驱动及电机简介
1.TB6612电机驱动基础引脚简介
AIN1和AIN2功能:电机输出端 控制电机转动方向
PWM功能:控制信号输入 控制电机转速
STBY功能:正常工作\待机状态控制端
一个使能信号端,当 STBY=1时,正常工作,输入PWM信号,电机可正常运行;当 STBY=0 时,电机驱动处于待机状态,输入信号,电机不会运行。(一般接3.3V即可)
E1A E1B:编码器A B相 编码器原理运用 见下方链接
ADC:模数转换器 用于获取电池电压
2.MG513电机 霍尔编码器
具体见下方链接
二.STM32f103VET6电机驱动代码简介
1.电机初始化(驱动两个电机)
/*
PE4 --->AIN1 PE2 --->AIN2
PE5 --->BIN1 PE6 --->BIN2
*/
void Motor_Init(void) //电机初始化
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体GPIO_InitStructure
RCC_APB2PeriphClockCmd(AIN1_GPIO_CLK, ENABLE); // 使能PE端口时钟
//选择io口
GPIO_InitStructure.GPIO_Pin = AIN1_GPIO_PIN| AIN2_GPIO_PIN|BIN1_GPIO_PIN|BIN2_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出,增大电流输出能力
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度
GPIO_Init(GPIOE, &GPIO_InitStructure); //GBIOE初始化
}
电机转动方向控制代码 对应上述tb6612真值表
void Motor_start(int8_t mode) //电机模式选择 正转 反转
{
if(mode==1) //正转
{
GPIO_SetBits(AIN1_GPIO_PORT, AIN1_GPIO_PIN); // 高电平 PE4 --- AIN1 1
GPIO_ResetBits(AIN2_GPIO_PORT, AIN2_GPIO_PIN); // 低电平 PE2 --- AIN2 0
GPIO_SetBits(BIN1_GPIO_PORT, BIN1_GPIO_PIN); // 高电平 PE5 --- BIN1 1
GPIO_ResetBits(BIN2_GPIO_PORT, BIN2_GPIO_PIN); // 低电平 PE6 --- BIN2 0
}
else if(mode==0) //反转
{
GPIO_ResetBits(AIN1_GPIO_PORT, AIN1_GPIO_PIN); // 低电平 PE4 --- AIN1 0
GPIO_SetBits(AIN2_GPIO_PORT, AIN2_GPIO_PIN); // 高电平 PE2 --- AIN2 1
GPIO_ResetBits(BIN1_GPIO_PORT, BIN1_GPIO_PIN); // 低电平 PE5 --- BIN1 0
GPIO_SetBits(BIN2_GPIO_PORT, BIN2_GPIO_PIN); // 高电平 PE6 --- BIN2 1
}
}
void Motor_stop(void) //电机模式 停止
{
GPIO_ResetBits(AIN1_GPIO_PORT, AIN1_GPIO_PIN); // 低电平 PE4 --- AIN1 0
GPIO_ResetBits(AIN2_GPIO_PORT, AIN2_GPIO_PIN); // 低电平 PE2 --- AIN2 0
GPIO_ResetBits(BIN1_GPIO_PORT, BIN1_GPIO_PIN); // 低电平 PE5 --- BIN1 0
GPIO_ResetBits(BIN2_GPIO_PORT, BIN2_GPIO_PIN); // 低电平 PE6 --- BIN2 0
}
motor.h 宏定义
#define AIN1_GPIO_PIN GPIO_Pin_4
#define AIN1_GPIO_PORT GPIOE
#define AIN1_GPIO_CLK RCC_APB2Periph_GPIOE
#define AIN2_GPIO_PIN GPIO_Pin_2
#define AIN2_GPIO_PORT GPIOE
#define AIN2_GPIO_CLK RCC_APB2Periph_GPIOE
#define BIN1_GPIO_PIN GPIO_Pin_5
#define BIN1_GPIO_PORT GPIOE
#define BIN1_GPIO_CLK RCC_APB2Periph_GPIOE
#define BIN2_GPIO_PIN GPIO_Pin_6
#define BIN2_GPIO_PORT GPIOE
#define BIN2_GPIO_CLK RCC_APB2Periph_GPIOE
void Motor_Init(void); //电机初始化
void Motor_start(int8_t mode); //电机模式选择 正转 反转
void Motor_stop(void); //电机模式 停止
2.配置PWM
默认配置系统时钟为72M
//pwm初始化 参数: arr:设为一个时钟频率的最大值 psc: 预分频值
//以PWM_Int(7199,0)为例;
//初始化pwm输出 72000 000/psc+1=72000 000 记一个数时间为1/7200 0000
//每(7199+1)个计数中断一次 7200 0000/7199+1=10000
//从0开始的 所以要+1
void PWM_Int(uint16_t arr,uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体GPIO_InitStructure
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体TIM_TimeBaseStructure
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体TIM_OCInitStructure
RCC_APB2PeriphClockCmd(PWMA_GPIO_CLK,ENABLE); //使能PA端口时钟
RCC_APB2PeriphClockCmd(PWMA_TIM_CLK,ENABLE); //使能定时器1
RCC_APB1PeriphClockCmd(PWMB_TIM_CLK,ENABLE); //使能定时器2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用模式输出
GPIO_InitStructure.GPIO_Pin = PWMA_GPIO_PIN; //PA8
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz; //IO口速度
GPIO_Init(PWMA_GPIO_PORT,&GPIO_InitStructure); //GPIO初始化
//设置自动重装载寄存器的值 决定每多少个数 记一次中断 即决定占空比的周期 CRR计数器决定占空比
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc; //预分频值 决定计一个数的时间
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(PWMA_TIM,&TIM_TimeBaseStructure); //配置定时器1
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1; //PWM脉冲宽度调制1
TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置TIM输出极性为高
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OC1Init(PWMA_TIM,&TIM_OCInitStructure); //定时器1通道1初始化
TIM_CtrlPWMOutputs(PWMA_TIM,ENABLE); //主输出使能
TIM_OC1PreloadConfig(PWMA_TIM,TIM_OCPreload_Enable); //启用TIM1外设的预装载寄存器
TIM_ARRPreloadConfig(PWMA_TIM,ENABLE); //使能自动装载允许位
TIM_Cmd(PWMA_TIM,ENABLE); //启动定时器1
//TIM2
GPIO_InitStructure.GPIO_Pin = PWMB_GPIO_PIN;
GPIO_Init(PWMB_GPIO_PORT,&GPIO_InitStructure); //GPIO初始化
TIM_TimeBaseInit(PWMB_TIM,&TIM_TimeBaseStructure); //配置定时器2
TIM_OC2Init(PWMB_TIM,&TIM_OCInitStructure); //定时器2通道2初始化
TIM_CtrlPWMOutputs(PWMB_TIM,ENABLE); //主输出使能
TIM_OC2PreloadConfig(PWMB_TIM,TIM_OCPreload_Enable); //使能预装载寄存器
TIM_ARRPreloadConfig(PWMB_TIM,ENABLE); //使能自动装载允许位
TIM_Cmd(PWMB_TIM,ENABLE); //启动定时器1
}
设置占空比函数
void Set_PWMA(int PWM) //设置占空比函数 PWM最大不能超过7199
{
TIM_SetCompare1(PWMA_TIM,PWM);//设置TIM1通道1的占空比 = pwm/7200
}
void Set_PWMB(int PWM)
{
TIM_SetCompare2(PWMB_TIM,PWM); //设置占空比函数
}
pwm.h 宏定义
#define PWMA_GPIO_PIN GPIO_Pin_8
#define PWMA_GPIO_PORT GPIOA
#define PWMA_GPIO_CLK RCC_APB2Periph_GPIOA
#define PWMA_TIM TIM1
#define PWMA_TIM_CLK RCC_APB2Periph_TIM1
#define PWMB_GPIO_PIN GPIO_Pin_1
#define PWMB_GPIO_PORT GPIOA
#define PWMB_GPIO_CLK RCC_APB2Periph_GPIOA
#define PWMB_TIM TIM2
#define PWMB_TIM_CLK RCC_APB1Periph_TIM2
void PWM_Int(uint16_t arr,uint16_t psc); //PWM 初始化函数
void Set_PWMA(int PWM); //设置占空比函数
void Set_PWMB(int PWM); //设置占空比函数
3.主函数(开环控制)
#include "stm32f10x.h"
#include "stm32f10x_it.h"
#include "systick.h"
#include "motor.h"
#include "pwm.h"
int main(void)
{
SystemInit(); //配置系统时钟为72M
Motor_Init(); //电机gpio初始化
PWM_Int(7199,0); //电机pwm初始化
Motor_start(0); //控制电机反转
SysTick_Delay_Ms(100); //延时100ms 等待初始化
while(1)
{
Set_PWMA(3600); //设置占空比函数 3600/7200=1/2
Set_PWMB(3600);
}
}
三.STM32f103VET6 霍尔编码器PID闭环控制
1.配置编码器
采用定时器的输入捕获功能进行编码器脉冲的采集。(也可以采用外部中断 见下方链接)
配置编码器GPIO函数
void Encoder_GPIO_Config(void) //编码器GPIO初始化 重定义TIM3和TIM4引脚
{
GPIO_InitTypeDef GPIO_InitStructure; //定义一个引脚初始化的结构体
RCC_APB2PeriphClockCmd(E1A_GPIO_CLK, ENABLE); //使能GPIOD时钟
RCC_APB1PeriphClockCmd(E1A_TIM_CLK, ENABLE); //使能TIM4时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO时钟
GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE); //重映射TIM4
GPIO_InitStructure.GPIO_Pin = E1A_GPIO_PIN|E1B_GPIO_PIN; //PD13、PD12
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(E1A_GPIO_PORT, &GPIO_InitStructure); //根据GPIO_InitStructure的参数初始化
RCC_APB2PeriphClockCmd(E2A_GPIO_CLK, ENABLE); //使能GPIOC时钟
RCC_APB1PeriphClockCmd(E2A_TIM_CLK, ENABLE); //使能TIM3时钟
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE); //重映射TIM4
GPIO_InitStructure.GPIO_Pin = E2A_GPIO_PIN|E2B_GPIO_PIN; //PC6、PC7
GPIO_Init(E2A_GPIO_PORT, &GPIO_InitStructure); //根据GPIO_InitStructure的参数初始化
}
将定时器配置为编码器接口模式 配置定时器中断优先级NVIC
//把TIM3,TIM4初始化为编码器接口模式
void Encoder_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定义一个定时器初始化的结构体
TIM_ICInitTypeDef TIM_ICInitStructure; //定义一个定时器编码器模式初始化的结构体
RCC_APB1PeriphClockCmd(E1A_TIM_CLK,ENABLE); //使能E1的TIM4时钟
RCC_APB2PeriphClockCmd(E2A_TIM_CLK,ENABLE); //使能E2的TIM3时钟
//时基配置
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); //清除之前配置
TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
//根据TIM_TimeBaseInitStruct的参数 初始化定时器TIM4
TIM_TimeBaseInit(E1A_TIM, &TIM_TimeBaseStructure);
//输入捕获结构体初始化
TIM_ICStructInit(&TIM_ICInitStructure); //把TIM_ICInitStruct 中的每一个参数按缺省值填入
TIM_EncoderInterfaceConfig(E1A_TIM, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);//使用编码器模式3:CH1、CH2同时计数,四分频
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器长度
//将TIM_ICInitStructure参数初始化定时器TIM4编码器模式
TIM_ICInit(E1A_TIM, &TIM_ICInitStructure);
TIM_ClearFlag(E1A_TIM, TIM_FLAG_Update); //清除TIM的更新标志位
TIM_ITConfig(E1A_TIM, TIM_IT_Update, ENABLE); //启用定时器中断
TIM_SetCounter(E1A_TIM,0); //定时器计数值清0
TIM_Cmd(E1A_TIM, ENABLE); //使能定时器4
//TIM3
//根据TIM_TimeBaseInitStruct的参数初始化定时器TIM3
TIM_TimeBaseInit(E2A_TIM, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(E2A_TIM, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);//使用编码器模式3:CH1、CH2同时计数,四分频
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器长度
//TIM_ICInitStructure参数初始化定时器TIM4编码器模式
TIM_ICInit(E2A_TIM, &TIM_ICInitStructure);
TIM_ITConfig(E2A_TIM, TIM_IT_Update, ENABLE); //启用定时器中断
TIM_ClearFlag(E2A_TIM, TIM_FLAG_Update); //清除TIM的更新标志位
TIM_SetCounter(E2A_TIM,0); //定时器计数值清0
TIM_Cmd(E2A_TIM, ENABLE); //使能定时器3
}
static void TIM3_NVIC_Config(void) //TIM3中断设置
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void TIM4_NVIC_Config(void) //TIM4中断设置
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
编码器脉冲数读取函数
void Read_Encoder_L(void)
{
Encoder_L= TIM_GetCounter(TIM4); //获取外部计数 四倍频要除以4
if(Encoder_L>0x8000)Encoder_L=Encoder_L-0xffff; //判断方向
//大于0xffff的一半 即为负数 因为CNT计数器值为int16 最大为0x8000
TIM_SetCounter(TIM4,0); //清除外部计数
}
void Read_Encoder_R(void)
{
Encoder_R= TIM_GetCounter(TIM3); //获取外部计数 四倍频要除以4
if(Encoder_R>0x8000)Encoder_R=Encoder_R-0xffff;
//大于0xffff的一半 即为负数 因为CNT计数器值为int16 最大为0x8000
TIM_SetCounter(TIM3,0); //清除外部计数
}
Encoder.h宏定义
#define E1A_GPIO_PIN GPIO_Pin_13
#define E1A_GPIO_PORT GPIOD
#define E1A_GPIO_CLK RCC_APB2Periph_GPIOD
#define E1A_TIM TIM4
#define E1A_TIM_CLK RCC_APB1Periph_TIM4
#define E1B_GPIO_PIN GPIO_Pin_12
#define E1B_GPIO_PORT GPIOD
#define E1B_GPIO_CLK RCC_APB2Periph_GPIOD
#define E1B_TIM TIM4
#define E1B_TIM_CLK RCC_APB1Periph_TIM4
#define E2A_GPIO_PIN GPIO_Pin_6
#define E2A_GPIO_PORT GPIOC
#define E2A_GPIO_CLK RCC_APB2Periph_GPIOC
#define E2A_TIM TIM3
#define E2A_TIM_CLK RCC_APB1Periph_TIM3
#define E2B_GPIO_PIN GPIO_Pin_7
#define E2B_GPIO_PORT GPIOC
#define E2B_GPIO_CLK RCC_APB2Periph_GPIOC
#define E2B_TIM TIM8
#define E2B_TIM_CLK RCC_APB2Periph_TIM8
void Encoder_Init(void); //初始化编码器
static void TIM3_NVIC_Config(void);
static void TIM4_NVIC_Config(void);
void Encoder_Init(void);
2.增量式PI闭环控制代码
可以先了解位置式和增量式PID的概念和线速度计算 上方连接
增量式PI速度环
/增量式PI控制速度环/
//编码器线数13ppr*30(减速比)=390 轮子转一圈有390个脉冲 四倍频要除以4
//速度计算公式:=脉冲数/390/4*0.53407(轮子周长)*0.01(单位时间) 单位m/s
int16_t Left_Goal;
int16_t Left_Current_Error; //当前误差
int16_t Left_Last_Error = 0; //上次误差
int16_t Left_Previous_Error = 0; //上上次误差
float Left_P = 0.2;
float Left_I = 0.80;
float Left_Speed_PID_OUT; //电机脉冲输出 OUT/7200 = 占空比
int16_t Left_Pro_Speed_PID_OUT; //限幅输出
float Left_Speed = 0.00; //左轮转速 m/s
int16_t Left_Speed_Control(float Goal,int16_t max_left,int16_t min_left) //左电机PID控制
{
Left_Goal = (int16_t)(Goal * 29.21); //速度转编码器
Left_Current_Error = Left_Goal - Encoder_L; //计算当前误差
Left_Speed_PID_OUT += ((Left_P * (Left_Current_Error - Left_Last_Error)) +
Left_I * Left_Current_Error);
//更新上次误差和上上次误差
Left_Previous_Error = Left_Last_Error;
Left_Last_Error = Left_Current_Error;
Left_Pro_Speed_PID_OUT =
(int16_t)Range_protect_float(Left_Speed_PID_OUT,max_left,min_left); //限幅保护函数
Set_PWMA(Left_Pro_Speed_PID_OUT);
return Left_Pro_Speed_PID_OUT;
}
3.定时器配置
要让mcu每10ms读取一次编码器脉冲 则需要开启mcu的定时器进行精准定时
定时器配置代码 配置一个1ms的定时器
uint32_t time = 0;
// 中断优先级配置
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器周期为7200 0000/(71+1)/(999+1)=1000 一秒1000个脉冲 1个脉冲1ms
void BASIC_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=72M
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;
// 时钟预分频数为
TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
// 初始化定时器
TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
// 清除计数器中断标志位
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
// 开启计数器中断
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
// 使能计数器
TIM_Cmd(BASIC_TIM, ENABLE);
}
void BASIC_TIM_Init(void)
{
BASIC_TIM_NVIC_Config(); //中断优先级配置
BASIC_TIM_Mode_Config(); //定时器配置
}
tim.h宏定义
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd //定义开启外设时钟函数
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Period 1000-1 //重装载的值
#define BASIC_TIM_Prescaler 71 //时钟预分频数
#define BASIC_TIM_IRQ TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
void BASIC_TIM_Init(void);
extern uint32_t time;
4.中断处理函数
主函数main配置好初始化代码即可 oled显示是另外增加的 下面函数放 it中断处理文件中
void BASIC_TIM_IRQHandler (void)
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) //获取计数
{
time++;
Key_Proc(); //按键扫描
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update); //清除计数
if(time==10)
{
if(flag==1)
{
Read_Encoder_L(); //读取编码器脉冲数
Left_Speed_Control(1.0,4000,-4000);
OLED_ShowNum(10,0,Encoder_L,4,12);//显示数字
}
if(flag==0)
{
Read_Encoder_L(); //读取编码器脉冲数
Left_Speed_Control(0.5,4000,-4000);
OLED_ShowNum(10,0,Encoder_L,4,12);//显示数字
}
time = 0;
}
}
}
实物图如上