题目要求要用TI公司的MCU,MSP系列的单片机。这里用stm32来实现所以功能,因为msp接触比较少就不分享了,msp430网上资料比较多比较容易上手。
我们主控用的是stm32rct6,这个单片机很强大有8个定时器,对于小车的运动控制要两个编码器模式的定时器来获取小车的每个时刻的脉冲,得到这个就可以得到当前轮子的实时速度了。要一个通用定时器来输出pwm波控制电机,要一个基本计时器来产生一个10ms中断来进行pid调节和脉冲的读取。这个中断也可以用pwm波的定时器来产生。多翻数据手册(不是中文参考手册)确定自己的需求。
驱动模块用的是TB6612FN,这个芯片一定要把stby角拉高才能驱动,vm是电机的额定功率,vcc是单片机高电平的电压看你是5v,还是3.3v,其实接3.3v就可以了。
下面这个图是例子
兼容性:
关于定时器的引脚分配,这篇文章分享:
STM32定时器要点-电子工程世界 (eeworld.com.cn)
其实有一个点一定一定要注意:
编码器是5v输入,要看定时器的配置引脚是不是满足5v的输入条件,配置相应的模式!!!(比赛的时候因为这个错误莫名其妙烧了芯片发现不出来,在快结束的时候烧了,这个bug不会一下就烧你芯片但是出现了就是致命的)
软件上的控制就这么多,上干货:
encoder.c
/**************************************************************************
函数功能:把TIM4初始化为编码器接口模式
入口参数:无
返回 值:无
**************************************************************************/
void Encoder_Init_TIM4(void)
{
RCC->APB1ENR|=1<<2; //TIM4时钟使能
RCC->APB2ENR|=1<<3; //使能PORTb时钟
GPIOB->CRL&=0X00FFFFFF;//PB6 PB7
GPIOB->CRL|=0X44000000;//浮空输入
/* 把定时器初始化为编码器模式 */
TIM4->PSC = 0x0;//预分频器
TIM4->ARR = ENCODER_TIM_PERIOD-1;//设定计数器自动重装值
TIM4->CCMR1 |= 1<<0; //输入模式,IC1FP1映射到TI1上
TIM4->CCMR1 |= 1<<8; //输入模式,IC2FP2映射到TI2上
TIM4->CCER |= 0<<1; //IC1不反向
TIM4->CCER |= 0<<5; //IC2不反向
TIM4->SMCR |= 3<<0; //SMS='011' 所有的输入均在上升沿和下降沿有效
TIM4->CR1 |= 0x01; //CEN=1,使能定时器
}
/**************************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回 值:速度值
**************************************************************************/
int Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break;
case 3: Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;break;
case 4: Encoder_TIM= (short)TIM4 -> CNT; TIM4 -> CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
另外一个定时器自己写一下
encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
#include <sys.h>
/**************************************************************************/
#define ENCODER_TIM_PERIOD (u16)(65535) //不可大于65535 因为F103的定时器是16位的。
void Encoder_Init_TIM2(void);
void Encoder_Init_TIM4(void);
int Read_Encoder(u8 TIMX);
#endif
pwm波的输出
PWM.C
void MiniBalance_PWM_Init(u16 arr,u16 psc)
{
// MiniBalance_Motor_Init(); //初始化电机控制所需IO
RCC->APB1ENR|=1<<1; //TIM3时钟使能
RCC->APB2ENR|=1<<3; //PORTB时钟使能
GPIOB->CRL&=0XFFFFFF00; //PORTB0 1复用输出
GPIOB->CRL|=0X000000BB; //PORTB0 1复用输出
TIM3->ARR=arr;//设定计数器自动重装值
TIM3->PSC=psc;//预分频器不分频
TIM3->CCMR2|=6<<12;//CH4 PWM1模式
TIM3->CCMR2|=6<<4; //CH3 PWM1模式
TIM3->CCMR2|=1<<11;//CH4预装载使能
TIM3->CCMR2|=1<<3; //CH3预装载使能
TIM3->CCER|=1<<12; //CH4输出使能
TIM3->CCER|=1<<8; //CH3输出使能
TIM3->CR1=0x8000; //ARPE使能
TIM3->CR1|=0x01; //使能定时器3
}
PWM.h
#ifndef __MOTOR_H
#define __MOTOR_H
#include <sys.h>
#define PWMA TIM3->CCR4
#define AIN2 PBout(15)
#define AIN1 PBout(14)
//#define BIN2 PCout(5)
#define BIN2 PBout(13)
#define BIN1 PBout(12)
#define AIN4 PCout(6)
#define AIN3 PCout(7)
#define BIN3 PCout(8)
#define BIN4 PCout(9)
#define PWMB TIM3->CCR3
void MiniBalance_PWM_Init(u16 arr,u16 psc);
void MiniBalance_Motor_Init(void);
#endif
电机驱动的引脚自己定义一下,随意一个四个引脚输出高低电平就可以控制正反转了。
然后产生一个10ms的中断,后来主控坏了只能用c8t6只有四个定时器就只能用time1来进行了
中断里面不仅跑的是采样,而且加入的pid,同时也要对pwm波进行限幅保护电机,同时控制电机正反转。
control.c
//int Target_velocity=30; //设定速度控制的目标速度为50个脉冲每10ms
//int Target_velocity1=30; //设定速度控制的目标速度为50个脉冲每10ms
int TIM1_UP_IRQHandler(void)
{
if(TIM1->SR&0X0001)//5ms定时中断
{
encoder();
PWMA_TASK(20);
PWMB_TASK(20);
}
return 0;
}
void encoder(void){
TIM1->SR&=~(1<<0); //===清除定时器1中断标志位
Encoder1=Read_Encoder(2); //===读取编码器的值,M法测速,输出为每10ms的脉冲数
Encoder2=Read_Encoder(4); //===读取编码器的值,M法测速,输出为每10ms的脉冲数
}
/**************************************************************************
函数功能:速度控制函数,最大60左右.不能超过(5v小电机)
入口参数:脉冲
返回 值:无
**************************************************************************/
void PWMA_TASK(int Target_velocity){
Xianfu_Pwm(); //===PWM限幅
Moto1=Incremental_PI(Encoder1,Target_velocity); //===速度PI控制器
// Moto1=Encoder1;
Set_Pwm1(Moto1); //===赋值给PWM寄存器
}
void PWMB_TASK(int Target_velocity){
Xianfu_Pwm(); //===PWM限幅
Moto2=Incremental_PI(Encoder2,Target_velocity); //===速度PI控制器
Set_Pwm2(Moto2); //===赋值给PWM寄存器
}
/**************************************************************************
函数功能:赋值给PWM寄存器
入口参数:PWM
返回 值:无
**************************************************************************/
void Set_Pwm1(int moto1)
{
if(moto1<0) AIN2=0, AIN1=1,AIN4=0, AIN3=1;
else AIN2=1, AIN1=0,AIN4=1, AIN3=0;
PWMA=myabs(moto1);
PWMC=myabs(moto1);
}
void Set_Pwm2(int moto2)
{
if(moto2<0) BIN2=0, BIN1=1,BIN4=0, BIN3=1;
else BIN2=1, BIN1=0,BIN4=1, BIN3=0;
PWMB=myabs(moto2);
PWMD=myabs(moto2);
}
/**************************************************************************
函数功能:限制PWM赋值
入口参数:无
返回 值:无
**************************************************************************/
void Xianfu_Pwm(void)
{
int Amplitude=7100; //===PWM满幅是7200 限制在7100
if(Moto1<-Amplitude) Moto1=-Amplitude;
if(Moto1>Amplitude) Moto1=Amplitude;
if(Moto2<-Amplitude) Moto2=-Amplitude;
if(Moto2>Amplitude) Moto2=Amplitude;
}
/**************************************************************************
函数功能:绝对值函数
入口参数:int
返回 值:unsigned int
**************************************************************************/
int myabs(int a)
{
int temp;
if(a<0) temp=-a;
else temp=a;
return temp;
}
/**************************************************************************
函数功能:增量PI控制器
入口参数:编码器测量值,目标速度
返回 值:电机PWM
根据增量式离散PID公式
pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k)代表本次偏差
e(k-1)代表上一次的偏差 以此类推
pwm代表增量输出
在我们的速度控制闭环系统里面,只使用PI控制
pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
**************************************************************************/
int Incremental_PI (int Encoder,int Target)
{
float Kp=120,Ki=100;
static int Bias,Pwm,Last_bias;
Bias=Encoder-Target; //计算偏差
Pwm+=Kp*(Bias-Last_bias)+Ki*Bias; //增量式PI控制器
Last_bias=Bias; //保存上一次偏差
return Pwm; //增量输出
}
control.h
#ifndef __CONTROL_H
#define __CONTROL_H
#include "sys.h"
/**************************************************************************
**************************************************************************/
#define PI 3.14159265
int TIM1_UP_IRQHandler(void);
void Set_Pwm1(int moto1);
void Set_Pwm2(int moto2);
void Xianfu_Pwm(void);
int myabs(int a);
int Incremental_PI (int Encoder,int Target);
void PWMA_TASK(int Target_velocity);
void PWMB_TASK(int Target_velocity);
void encoder(void);
#endif
基本对于小车运动控制就在这里了,基本上所以的坑我们都踩过了,
最后一些惨痛教训:
1.不能带pid加系统的,小车系统每加一个模块就要重新调pid,所以pid是最后调的,先开环把所以功能先实现,要不然你不知道是pid的问题还是你本身模块驱动的问题或是接线的问题。切记切记!
2.在做设计的时候要留有余量,特别是比赛的时候,用c8t6是把芯片榨干了,但是最后加模块的时候就很痛苦没有资源了,rc可以满足要求也相对比较便宜。