1.什么是PID?
PID知识学习指南:
https://www.bilibili.com/video/BV1Ds411t7Hr
2.为什么要用PID来对电机进行控制?
https://www.zhihu.com/question/353181256/answer/883991821
3.PWM输出配置
通过以上两个链接的学习,我们知道了使用PID对电机速度进行控制,就是通过调节PWM方波的占空比来对电机的速度进行调节。接下来我们就介绍PWM输出是如何配置的。
void TIM8_PWM_Init(u32 arr,u32 psc)//运用TIM8输出PWM方波
{
GPIO_InitTypeDef GPIO_InitStructure; //定义初始化GPIO口的结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE); //TIM8时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //使能PORTC时钟
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8); //GPIOF8复用为定时器
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOF8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PF8
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 初始化定时器的时基单元
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);//初始化定时器8
//初始化TIM14 Channel1 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1 设置为模式一即小于CCRX为有效
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse =250; //CCRx
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出的有效极性 高
TIM_OC3Init(TIM8, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM8 OC3
TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); //使能TIM8在CCR1上的预装载寄存器
TIM_ARRPreloadConfig(TIM8,ENABLE);//ARPE使能 //这个周期生效或下个周起生效
TIM_Cmd(TIM8, ENABLE); //使能TIM8
TIM_CtrlPWMOutputs(TIM8, ENABLE); //高级定时器必须加这句才能输出PWM!!!!!!
}
注意:
STM32FF4和STM32F1引脚复用功能配置不相同,F4为“GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8);”同时注意需要将引脚配置为复用功能。
使用高级定时器输出PWM时需要,在最后加一句TIM_CtrlPWMOutputs(TIM8, ENABLE);才能输出PWM通用编码器可以不用加。
4.怎么配置TIM定时器为编码模式
说完了PWM输出怎么配置,接下来我们说说如何运用定时器的编码器模式对电机编码器进行读值。
想对定时器,编码模式进行深入了解可根据《STM32F4中文参考手册》进行了解。
void encoder_tim3_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; // GPIO口的输入模式配置很重要,不正确的话会读不到数据
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
//开时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //开启GPIOx口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3作为编码器读取
// GPIO口初始化
//初始化编码器的GPIO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //设置为为复用
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //设置为开漏
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//指定操作管脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3); //复用功能配置
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3); //将TIM3接到AF2 将PA6 PA7映射到TIM3时钟线上
// 定时器编码器模式初始化
//配置时基单元
TIM_DeInit(TIM3); //复位TIM3相关寄存器
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_Down; // 这里可以自己设置,或使用默认值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//此处:TIM_ICPolarity_Rising 意思是不反相
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); // 这里配置了编码器模式
//配置编码器
TIM_ICStructInit(&TIM_ICInitStructure); //该结构中按缺省值(默认)填入
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1 |TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection =TIM_ICSelection_DirectTI; //设置
TIM_ICInitStructure.TIM_ICFilter = 0; //配置滤波器值
TIM_ICInit(TIM3, &TIM_ICInitStructure); //设置通道
TIM_SetCounter(TIM3, 0);
TIM_Cmd(TIM3, ENABLE);
}
5.该怎么吧TIM的CNT中的值读出来呢
因为读值我们不能时时刻刻都对编码器的计数器进行读值这样出来的数值意义不大所以我们需要没10ms对定时器的计数器中进行读值。(B站视频中有说明)
由于我们需要有一个10ms的定时所以我们还需要开启一个定时器来计时每10ms去读一次。并且我们可以定义一个全局变量,用来接收计数器中的数值。
void TIM2_IRQHandler(void)
{
extern int encoder_num;
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //判断中断是否溢出 //注意将TIM2改正确
{
encoder_num = (int)((int16_t)(TIM3-》CNT)); // 这里尤其需要注意数据类型
TIM_SetCounter(TIM3, 0);
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
}
这里对定时器的配置不加以赘述了。(使用的计数器为TIM2)
这里encoder_num就是全局变量。
注意:
1.若编码器读出来的值为0xEEEEExx,说明该电机为反转。
最后:我们通过1中视频的讲解直接将该全局变量的值作为当前值,按照B站视频中的PID方式编写控制函数即可。
移植例程踩过的坑
检查开时钟时钟 函数名,总线名,传入参数 是否对应改变了。
中断服务函数中判断溢出是否更换了,判断的时钟 。
是否清楚了中断标志位 。
1.什么是PID?
PID知识学习指南:
https://www.bilibili.com/video/BV1Ds411t7Hr
2.为什么要用PID来对电机进行控制?
https://www.zhihu.com/question/353181256/answer/883991821
3.PWM输出配置
通过以上两个链接的学习,我们知道了使用PID对电机速度进行控制,就是通过调节PWM方波的占空比来对电机的速度进行调节。接下来我们就介绍PWM输出是如何配置的。
void TIM8_PWM_Init(u32 arr,u32 psc)//运用TIM8输出PWM方波
{
GPIO_InitTypeDef GPIO_InitStructure; //定义初始化GPIO口的结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE); //TIM8时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //使能PORTC时钟
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8); //GPIOF8复用为定时器
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOF8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PF8
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 初始化定时器的时基单元
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);//初始化定时器8
//初始化TIM14 Channel1 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1 设置为模式一即小于CCRX为有效
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse =250; //CCRx
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出的有效极性 高
TIM_OC3Init(TIM8, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM8 OC3
TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); //使能TIM8在CCR1上的预装载寄存器
TIM_ARRPreloadConfig(TIM8,ENABLE);//ARPE使能 //这个周期生效或下个周起生效
TIM_Cmd(TIM8, ENABLE); //使能TIM8
TIM_CtrlPWMOutputs(TIM8, ENABLE); //高级定时器必须加这句才能输出PWM!!!!!!
}
注意:
STM32FF4和STM32F1引脚复用功能配置不相同,F4为“GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8);”同时注意需要将引脚配置为复用功能。
使用高级定时器输出PWM时需要,在最后加一句TIM_CtrlPWMOutputs(TIM8, ENABLE);才能输出PWM通用编码器可以不用加。
4.怎么配置TIM定时器为编码模式
说完了PWM输出怎么配置,接下来我们说说如何运用定时器的编码器模式对电机编码器进行读值。
想对定时器,编码模式进行深入了解可根据《STM32F4中文参考手册》进行了解。
void encoder_tim3_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; // GPIO口的输入模式配置很重要,不正确的话会读不到数据
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
//开时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //开启GPIOx口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3作为编码器读取
// GPIO口初始化
//初始化编码器的GPIO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //设置为为复用
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //设置为开漏
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//指定操作管脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3); //复用功能配置
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3); //将TIM3接到AF2 将PA6 PA7映射到TIM3时钟线上
// 定时器编码器模式初始化
//配置时基单元
TIM_DeInit(TIM3); //复位TIM3相关寄存器
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_Down; // 这里可以自己设置,或使用默认值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//此处:TIM_ICPolarity_Rising 意思是不反相
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); // 这里配置了编码器模式
//配置编码器
TIM_ICStructInit(&TIM_ICInitStructure); //该结构中按缺省值(默认)填入
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1 |TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection =TIM_ICSelection_DirectTI; //设置
TIM_ICInitStructure.TIM_ICFilter = 0; //配置滤波器值
TIM_ICInit(TIM3, &TIM_ICInitStructure); //设置通道
TIM_SetCounter(TIM3, 0);
TIM_Cmd(TIM3, ENABLE);
}
5.该怎么吧TIM的CNT中的值读出来呢
因为读值我们不能时时刻刻都对编码器的计数器进行读值这样出来的数值意义不大所以我们需要没10ms对定时器的计数器中进行读值。(B站视频中有说明)
由于我们需要有一个10ms的定时所以我们还需要开启一个定时器来计时每10ms去读一次。并且我们可以定义一个全局变量,用来接收计数器中的数值。
void TIM2_IRQHandler(void)
{
extern int encoder_num;
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //判断中断是否溢出 //注意将TIM2改正确
{
encoder_num = (int)((int16_t)(TIM3-》CNT)); // 这里尤其需要注意数据类型
TIM_SetCounter(TIM3, 0);
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
}
这里对定时器的配置不加以赘述了。(使用的计数器为TIM2)
这里encoder_num就是全局变量。
注意:
1.若编码器读出来的值为0xEEEEExx,说明该电机为反转。
最后:我们通过1中视频的讲解直接将该全局变量的值作为当前值,按照B站视频中的PID方式编写控制函数即可。
移植例程踩过的坑
检查开时钟时钟 函数名,总线名,传入参数 是否对应改变了。
中断服务函数中判断溢出是否更换了,判断的时钟 。
是否清楚了中断标志位 。
举报