目录
【硬件说明】
【软件设计】
驱动端口初始化
理论公式说明
PG反馈信号输入捕获
TIM2初始化
定时器2中断程序
PWM引脚初始化
速度PID模型和位置PID模型
主程序
板子:STM32F103RCT6内核开发板;BLDC驱动器(型号UB510)
图1 驱动器驱动输入输出端口示意图
EN是低电平使能信号;GND是信号地;FR是方向控制信号,高低电平控制正反转;SV是PWM输入端,该 PWM 输入信号的占空比可为 0 到 100%任意值, PWM 频率应在 1KHz-20KHz 之间,频率偏向低则 PWM 精度高;BK是刹车信号,低电平刹车。
PG是霍尔信号输出端,PG 信号用于提供电机转速脉冲信号,PG 信号为开漏输出,在连接时,在 5V 与 PG加一个6.7KΩ电阻作上拉电阻。电机一个电周期内将在 PG 信号端输出 3 个脉冲。对于两对极的电机旋转一周将产生 6 个脉冲,四对极则为 12 个脉冲。本次使用的电机是5对极的,电机转动时在 PG 端输出的频率与转速关系如下式:
电机转速(RPM)=20 x PG 信号频率 / 电机极对数 ①
在程序进入while循环之前,需要对驱动EN、FR、BK端口进行端口初始化配置。
/**************************************************************************
函数功能:驱动端口初始化
入口参数:无
返回 值:无
**************************************************************************/
void QuDong_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体GPIO_InitStructure
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能PB端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能PC端口时钟
//EN2---PB15 EN1---PB13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 | GPIO_Pin_13; //PB15 PB13
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度
GPIO_Init(GPIOB, &GPIO_InitStructure); //GBIOB初始化
GPIO_ResetBits(GPIOB,GPIO_Pin_15 | GPIO_Pin_13); //置低使能
//FR2---PC3 FE1---PC2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_2; // PC3 PC2
GPIO_Init(GPIOC, &GPIO_InitStructure); //GBIOC初始化
GPIO_SetBits(GPIOC,GPIO_Pin_2); //置高使能
GPIO_ResetBits(GPIOC,GPIO_Pin_3); //置低使能
//BK2---PC1 BK1---PC0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0; // PC1 PC0
GPIO_Init(GPIOC, &GPIO_InitStructure); //GBIOC初始化
GPIO_SetBits(GPIOC,GPIO_Pin_1|GPIO_Pin_0); //置高不刹车
}
由于速度PID是根据编码器返回值与设定的脉冲频率相比较,通过增大输出PWM值来使电机驱动电压增大,转速加快,继而使得编码器返回值趋于设定的脉冲频率值,从而实现速度PID。所以第一个要解决的问题是:如何得到速度对应的脉冲频率值呢?苦想许久,我得出这样一个公式:
mc = v / 20.0 * 5.0 * 60.0 / 2.0 / 3.14 / 0.09 * 50.0 ②
v = value * 20.0 / 5.0 / 60.0 * 2.0 * 3.14 * 0.09 / 50.0 ③
其中50是电机减速比,v是设定的速度,mc是速度对应的脉冲值,value是编码器脉冲值。对于这条公式,要结合PG返回的脉冲值公式:
电机转速(RPM)=20 x PG 信号频率 / 电机极对数 ①
所以,我对于公式③这么解释:value*20.0/5.0/50.0得到的是每分钟电机转了多少转,再除以60得到每秒电机转了多少转。实际测得的轮子的半径为0.09m,所以每秒轮子走过的距离是:
value * 20.0 / 5.0 / 60.0 * 2.0 * 3.14 * 0.09 / 50.0
在实际过程中,PG信号的捕获至关重要,因为该驱动器应该是内置了一个编码器,通过PG信号的捕获,我们才能得到返回的编码器的值,而这个值根据上文所说,关系到能否计算得到速度PID。
得到PG信号的捕获由两种方式:一种是使用外部时钟计数器,一种是输入捕获。经过大量测试和适配,最终敲定采用输入捕获。
在程序进入主函数while循环之前,需要对TIM2进行初始化配置。定时器2输入捕获,开启TIM2_CH3、TIM2_CH4通道。
/**************************************************************************
函数功能:TIM2初始化
入口参数:无
返回 值:无
**************************************************************************/
void TIM2_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 需要使能AFIO时钟
GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); //引脚重映射
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //PB10 PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //10M时钟速度
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_DeInit(TIM2); //重新将Timer设置为缺省值
TIM_InternalClockConfig(TIM2); //采用内部时钟给TIM2提供时钟源
//初始化定时器2 TIM2
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //重复溢出次数,设置为0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ARRPreloadConfig(TIM2, DISABLE); //禁止ARR预装载缓冲器
//初始化TIM2输入捕获参数
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3; //选择输入端 IC3映射到TI3上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI3上
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 10; //配置IC3F输入滤波器 滤波
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; //选择输入端 IC4映射到TI4上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI4上
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 10; //配置IC4F输入滤波器 滤波
TIM_ICInit(TIM2, &TIM_ICInitStructure);
//中断分组初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级1级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ClearFlag(TIM2, TIM_FLAG_Update|TIM_IT_CC3); //清除溢出中断标志
TIM_ClearFlag(TIM2, TIM_FLAG_Update|TIM_IT_CC4); //清除溢出中断标志
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC3,ENABLE); //允许更新中断 ,允许CC3IE捕获中断
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC4,ENABLE); //允许更新中断 ,允许CC4IE捕获中断
TIM_Cmd(TIM2,ENABLE ); //使能定时器2
}
TIM_ValueTypeDef TIM2_CH3Structure;
TIM_ValueTypeDef TIM2_CH4Structure;
TIM_ValueTypeDef TIM2_CH3Structure={0,0,0,0,0,0,0};
TIM_ValueTypeDef TIM2_CH4Structure={0,0,0,0,0,0,0};
u32 Capture3[6];
u32 Capture4[6];
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)
{
TIM2_CH3Structure.Capture_Period++;
TIM2_CH4Structure.Capture_Period++;
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
}
if(TIM_GetITStatus(TIM2,TIM_IT_CC3) != RESET)
{
if(TIM2_CH3Structure.Capture_StartFlag==0)
{
TIM2_CH3Structure.Capture_Period=0;
TIM2_CH3Structure.Capture_StartFlag=1;
TIM2_CH3Structure.Capture_CcrValue=TIM_GetCapture3(TIM2);
}
else if(TIM2_CH3Structure.Capture_StartFlag == 1)
{
TIM2_CH3Structure.Capture_CcrValue1=TIM_GetCapture3(TIM2);
TIM2_CH3Structure.Capture_StartFlag=0;
TIM2_CH3Structure.value=1000000/(TIM2_CH3Structure.Capture_Period*65536+TIM2_CH3Structure.Capture_CcrValue1-TIM2_CH3Structure.Capture_CcrValue);
Capture3[(TIM2_CH3Structure.INDEX)++]=TIM2_CH3Structure.value;
if(TIM2_CH3Structure.INDEX==6)
{
TIM2_CH3Structure.INDEX=0;
TIM2_CH3Structure.Capture_FinishFlag=1;
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_CC3);
}
if(TIM_GetITStatus(TIM2,TIM_IT_CC4) != RESET)
{
if(TIM2_CH4Structure.Capture_StartFlag==0)
{
TIM2_CH4Structure.Capture_Period=0;
TIM2_CH4Structure.Capture_StartFlag=1;
TIM2_CH4Structure.Capture_CcrValue=TIM_GetCapture4(TIM2);
}
else if(TIM2_CH4Structure.Capture_StartFlag == 1)
{
TIM2_CH4Structure.Capture_CcrValue1=TIM_GetCapture4(TIM2);
TIM2_CH4Structure.Capture_StartFlag=0;
TIM2_CH4Structure.value=1000000/(TIM2_CH4Structure.Capture_Period*65536+TIM2_CH4Structure.Capture_CcrValue1-TIM2_CH4Structure.Capture_CcrValue);
Capture4[(TIM2_CH4Structure.INDEX)++]=TIM2_CH4Structure.value;
if(TIM2_CH4Structure.INDEX==6)
{
TIM2_CH4Structure.INDEX=0;
TIM2_CH4Structure.Capture_FinishFlag=1;
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_CC4);
}
}
在程序进入while循环之前,需要对连接SV端口的PWM引脚进行引脚初始化,PWM频率最高20K。
void MiniBalance_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体GPIO_InitStructure
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构TIM_TimeBaseStructure
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构TIM_OCInitStructure
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //使能定时器4时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽模式输出
GPIO_Init(GPIOB, &GPIO_InitStructure); //GBIOB初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWMTIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置TIM输出比较极性为高
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable; //比较输出使能
TIM_OC1Init(TIM4, &TIM_OCInitStructure); //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_OC2Init(TIM4, &TIM_OCInitStructure); //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_OC4Init(TIM4, &TIM_OCInitStructure); //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_ARRPreloadConfig(TIM4, ENABLE); //使能自动装载允许位
TIM_Cmd(TIM4, ENABLE); //启动定时器TIM4
}
/*********************
入口参数:编码器脉冲频率
返 回 值:该脉冲对应的速度值
**********************/
float get_v(short value)
{
float v=0;
v=value*20.0/5.0/60.0*2.0*3.14*0.09/50.0;
return v;
}
/*********************
入口参数:速度值
返 回 值:该速度对应的脉冲频率
**********************/
float get_mc(float v)
{
float mc=0;
mc=v/20.0*5.0*60.0/2.0/3.14/0.09*50.0;
return mc;
}
/**************************************************************************
函数功能:速度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代表增量输出
**************************************************************************/
int Velocity_PI(int Encoder, int v_Target)
{
static int Bias, Pwm, Last_bias, Lsater_bias;
Bias = Encoder - v_Target; //计算偏差
Pwm += Velocity_KP * (Bias - Last_bias)+ Velocity_KI * Bias; //增量式PI控制器
// if (Pwm > 7200)
// Pwm = 7200;
// if (Pwm < -7200)
// Pwm = -7200;
Lsater_bias = Last_bias; //保存上上一次偏差
Last_bias = Bias; //保存上一次偏差
return Pwm; //增量输出
}
/**************************************************************************
函数功能:位置PID
入口参数:已行驶路程,目标路程
返 回 值:电机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代表增量输出
**************************************************************************/
int Position_PID(float x_now, float x_Target)
{
static float Bias, Pwm, Integral_bias, Last_bias;
Bias = x_now - x_Target; //计算偏差
Integral_bias += Bias;
Pwm = Position_KP * Bias + Position_KI * Integral_bias + Position_KD * (Bias - Last_bias);
// if (Pwm > 7200)
// Pwm = 7200;
// if (Pwm < -7200)
// Pwm = -7200;
Last_bias = Bias; //保存上一次偏差
return Pwm; //增量输出
}
int main(void)
{
delay_init(); //延时函数初始化
MY_NVIC_PriorityGroupConfig(2); //设置中断分组
JTAG_Set(SWD_ENABLE); //打开SWD接口 可以利用主板的SWD接口调试
QuDong_Init(); //初始化驱动器驱动端口
uart_init(115200); //串口1初始化
CAN_Config(); //初始化can,在中断接收CAN数据包*/
MiniBalance_PWM_Init(3599,0); //PWM频率最高 20K
TIM2_Cap_Init(0XFFFF, 72-1); //定时器2 输入捕获 开启通道 TIM2_CH3 TIM2_CH4
TIM6_Int_Init(100 - 1, 7200 - 1); //定时器6初始化 10ms中断
printf("开始!\r\n");
v_mc = (int)get_mc(0.15); //输入速度值 转化位对应的脉冲频率
L=6.0;
/*** 硬件适配 ***/
TIM_SetCompare1(TIM4,0);
TIM_SetCompare2(TIM4,0);
delay_ms(250);
while(1)
{
TIM_SetCompare1(TIM4,sv_L);
TIM_SetCompare2(TIM4,sv_R*1.013);
printf("sv_L = %d\r\n",sv_L);
printf("sv_R = %d\r\n",sv_R);
if(TIM2_CH3Structure.Capture_FinishFlag == 1)
{
printf("Frequency_L = %d Hz ",TIM2_CH3Structure.value);
printf("V_L = %f M/s ",get_v(TIM2_CH3Structure.value));
printf("X_L = %f M \n",X_L);
TIM2_CH3Structure.Capture_FinishFlag = 0;
}
if(TIM2_CH4Structure.Capture_FinishFlag == 1)
{
printf("Frequency_R = %d Hz ",TIM2_CH4Structure.value);
printf("V_R = %f M/s ",get_v(TIM2_CH4Structure.value));
printf("X_R = %f M \n",X_R);
TIM2_CH4Structure.Capture_FinishFlag = 0;
}
else
{
printf("Wave is not exist or Cature is incomplete.\n");
}
delay_ms(1000);
}
}
}
void TIM6_IRQHandler(void) // TIM6 10ms中断
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) // 检查指定的TIM6中断发生与否
{
X_L += get_v(TIM2_CH3Structure.value)*0.01;
X_R += get_v(TIM2_CH4Structure.value)*0.01;
if(TIM2_CH3Structure.value <= v_mc && X_L <= (L-v_mc*0.008))
{sv_L=(int)(float)(Velocity_PI(TIM2_CH3Structure.value,v_mc)/-20000.0*3600.0);}
if(TIM2_CH4Structure.value <= v_mc && X_R <= (L-v_mc*0.008))
{sv_R=(int)(float)(Velocity_PI(TIM2_CH4Structure.value,v_mc)/-20000.0*3600.0);}
if(X_L>=(L-v_mc*0.008))
{sv_L=(int)(float)(Position_PID(X_L,L)/-20000.0*3600.0);}
if(X_R>=(L-v_mc*0.008))
{sv_R=(int)(float)(Position_PID(X_R,L)/-20000.0*3600.0);}
}
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除TIM6的中断待处理位
}
其中sv_R、sv_L的计算是对应输出的Pwm占空比的计算。
到此,电机PWM驱动模块的一些基本思路和代码已经罗列出来,其实很多程序雏形都是扒拉大佬的代码,自己理解之后修修改改的,我们都是站在巨人的肩膀上去看世界。
在下菜鸟,大家的鼓励是我继续创作的动力,如果觉得写的不错,欢迎关注,点赞,收藏,转发,谢谢!