上一篇文章已经能够把从mpu6050的数据通过stm32上传到匿名上位机进行实时波形显示,这一篇就来谈谈怎么让一个小车按照我们想要的状态动起来,这一篇也算是我弄了好几天才明白的东西做一个大概的介绍,算是来自一个小白的“吐槽”吧,哈哈哈,不多说,进入主题!(本文不介绍每一个模块的具体原理,只是介绍一下怎么用! )
首先我们先来了解一下我们要使用的电机:编码器电机,如图:
这是一款增量式输出的霍尔编码器,好用但有点贵,哈哈。电机线+电机线-是PWM的控制接口,来决定输出的速度是多少。其实如果不要使用编码器的话,完全可以当成一个普通的直流电机来使用。所以我们就用定时器3来控制PWM输出,代码如下:
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB , ENABLE); //使能GPIO外设时钟使能
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7 ; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1 ; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision =0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//TIM3通道一
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//TIM3通道二
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//TIM3通道三
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
//TIM3通道四
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_CtrlPWMOutputs(TIM3,ENABLE); //MOE 主输出使能
TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM1
TIM3_PWM_Init(7199,0);//10KHZ //主函数的初始化。使用时这段话不要放在一起,我这是为了介绍。
TIM_SetCompare1(TIM3,x) //使用方法,注意X不要大于7199,毕竟7199已经是满转状态
TIM_SetCompare2(TIM3,x)
TIM_SetCompare3(TIM3,x)
TIM_SetCompare4(TIM3,x)
我用的是L298N,这个用来驱动这个电机不是很好,发热很大,但是手头上没有tb6612,只能勉强的用一下。这个L298N我是接的外电源,就是想跟stm32的电源分开,但是记住,一定要共地,图片如下:
通过上面的已经能够让车轮动起来了,但是仅仅是作为一个普通的直流电机,还不是我们选择编码器电机想要的效果,所以我们就需要使用我们刚才没有用到的编码器。编码器是通过AB相来对外输出它检测到的数值。stm32有专门的编码器接口,所以我们无需额外配置,直接来用便可。代码如下:
void Encoder_Init_TIM2(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器4的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOB
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;////TIM向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_SetCounter(TIM2,0);
TIM_Cmd(TIM2, ENABLE);
}
因为一个定时器只能用计数一个编码器,所以还有一个TIM4,这里篇幅不够,可以自行加上。
/**************************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回 值:速度值
**************************************************************************/
int Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break;
case 4: Encoder_TIM= (short)TIM4 -> CNT; TIM4 -> CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
/**************************************************************************
函数功能:TIM2中断服务函数
入口参数:无
返回 值:无
**************************************************************************/
void TIM2_IRQHandler(void)
{
if(TIM2->SR&0X0001)//溢出中断
{
}
TIM2->SR&=~(1<<0);//清除中断标志位
}
通过上面的介绍以及代码配置,我们就能真正的使用这一款编码器电机!
这里有一篇文章介绍编码器的,我觉得挺好https://blog.csdn.net/weixin_44430455/article/details/97790029?ops_request_misc={%22request_id%22:%22158174719519724847018744%22,%22scm%22:%2220140713.130056874…%22}
介绍到这里,平衡车可以组装起来了。接下来就是PID的调试了。先上两篇我觉得比较好的文章https://blog.csdn.net/zhaoyuaiweide/article/details/54572483
https://blog.csdn.net/ReCclay/article/details/84789455
下一篇文章讲讲平衡车的pid如何使用调试,仅仅是我的一些些想法。