文章目录[隐藏]
使用PID有三个重要的参数:设定输出量、实际输出量、偏差量。
比如设定了控制速度,需要测量电机实际输出的速度以获取偏差值,从而进行反馈控制,使得控制更加精准。
〇、补一点电机的知识
(1)减速比:
减速比=输入速度/输出速度
(2)624线:
电机每转一圈输出624个脉冲
(3)416RPM
RMP,rotation per minute,每分钟416转(圈)
一、如何控制小车的速度
使用PWM调节占空比从而调节它的速度
比如:
设置自动重装载值为ARR=3000,
TIM_TimeBaseStructure.TIM_Period = 3000;
设置比较值CCR=2000,
TIM_SetCompare2(TIM2,2000);
设置计数模式为向上计数,
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
设置极性为高电平有效,
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
因此得到的输出速度是做到速度的(3000-2000)/3000=33.3%。
二、 如何解决不能使用三个定时器的问题
控制小车的运动需要一个定时器(TIM2);需要两个测量电机实际输出速度的定时器(TIM3、TIM4),也即是读取一定时间内编码器的脉冲数。
然后就会出现一个玄学问题,一旦使用三个及以上的定时器就会卡住,原因没找到,但找到了一个解决方法就是再使用一个系统滴答定时器,并且在系统定时器中断里执行程序。
1、系统滴答计时器与普通定时器的区别:
(1)功能上,滴答计时器只有最基本的计时器的功能;而普通计时器有更多的功能
(2)使用方面,滴答计时器一般是用于操作系统的计时
(3)在中断优先级方面,滴答定时器不受外界干扰,稳定的进入中断;而普通的定时器受中断优先级的约束,可以被高优先级中断打断。
2、如何使用系统滴答定时器:
(1)初始化定时器(初始化要放在最后,不然它初始化后就开始计时,放它后面的外设初始化等可能初始化不了)
void SysTick_Init(void)
{
/* SystemFrequency / 100 10ms中断一次
SystemFrequency / 1000 1ms中断一次
SystemFrequency / 100000 10us中断一次
SystemFrequency / 1000000 1us中断一次
*/
SystemCoreClockUpdate();
if (SysTick_Config(SystemCoreClock/10 )) //0.1s中断一次
{
while (1);
}
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;// 开启滴答定时器
}
(2)然后在stm32f10x_it.c中的 SysTick_Handler(void)函数中写出中断要执行的程序即可
void SysTick_Handler(void)
{
Motor_Left(1);
Motor_Right(1);
GetMotorPulse();
}
三、使用正交编码器测量脉冲
1、正交编码器初始化代码:
(参考这位大佬的)
void TIM3_PWM_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOB
TIM_DeInit(TIM3);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频器
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//Reset counter
TIM_SetCounter(TIM3,0);
TIM_Cmd(TIM3, ENABLE);
}
2、关于正交编码器定时器初始化的几点:
(1)使用编码器模式只能使用每个定时器的通道1(CH1)和通道2(CH2)
因为定时器在捕获时,
而正交编码器在计数时,使用的是TI1和TI2,其对应的信号是TI1FP1和TI2FP2,所以只能用CH1和CH2。
(2) GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
这里上拉输入和浮空输入都可以,没啥大的影响。
(3) TIM_TimeBaseStructure.TIM_Period = arr;
之前学习的时候,看到很多大佬说自动重装值是编码器的线数,如果使用四倍频的话,那就是该值就是 “线数×4-1”。
后来研究了一下,没什么道理,与定时器模式不同,在编码器获取脉冲值的模式中,自动重装载值是一个上限,超过了就会溢出,如果数值太大溢出,那么我们读取的脉冲数就是错的,所以我们只要保证设置的值一定大于固定时间内的脉冲数即可。当然了,最稳妥的,难道不是65535吗?
(4) TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
定时器使用双通道检测模式(TIM_EncoderMode_TI12)
小车电机上的编码器会输出两列脉冲波,A相波和B相波(如下图)。
定时器的编码器有三种模式:
TIM_EncoderMode_TI1在TI1的边沿计数,只计A相脉冲
TIM_EncoderMode_TI2在TI2的边沿计数,只计B相脉冲
TIM_EncoderMode_TI12在TI1和TI2的边沿计数,计A、B相脉冲
使用模式3的话测出来的数据会更准确。
使用上升沿计数模式( TIM_ICPolarity_Rising)
测量A、B两列波的上升沿,那么实际脉冲值=获得的脉冲值/2
那为什么不使用双边沿呢,使用双边沿四倍频不是更精准了?因为不知道什么问题,使用双边沿得到的脉冲值大到离谱,如下图,左边的脉冲值是上升沿检测的,右边的脉冲值是双边沿检测的😅大概40m/s,按照一般高速的120公里时速计算,每分钟就是2000米,每秒就是33米多一点,如果双边沿脉冲检测的是对的,那么这小车上个高速绰绰有余。(奇怪的知识又增加了)
四、数据处理
实际脉冲值:
实际脉冲值=获得的脉冲值/2
对应的速度值:
代码块:
//小车参数的定义
#define span 0.1 //系统定时0.1s,每0.1秒计算一次
#define dis_circle 3.14*6.5 //小车轮胎直径6.5cm
#define everyciecle_encode 624//转一圈的编码器数=编码器的线数
//读取电机脉冲
void GetMotorPulse(void)
{
LeftMotorPulse = (short)TIM3->CNT/2;
TIM3->CNT = 0;
RightMotorPulse = (short)TIM4->CNT/2;
TIM4->CNT = 0;
LeftMotorSpeed=LeftMotorPulse/everyciecle_encode*dis_circle/span; //得到左车轮的速度
RightMotorSpeed=RightMotorPulse/everyciecle_encode*dis_circle/span; //得到右车轮的速度
printf("Pulse:%d %d\r\n",LeftMotorPulse,RightMotorPulse);
printf("Speed:%f %f\r\n",LeftMotorSpeed,RightMotorSpeed);
}
💜附上测试程序:STM32F1.ControlDetect
版权声明:本文为CSDN博主「刘一五」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Jessaly/article/details/122477109
暂无评论