主控芯片:STM32F103C8T6
电机型号:MD520Z30
电机驱动:TB6612
软件:CUBEMX
接线:
PA1==>>编码器A相
PA0==>>编码器B相
PA6==>>AIN1
PA7==>>AIN2
PB1==>>PWMA
①新建项目:
设置时钟72MHz
②配置定时器
TIM2设置为编码器模式:
TM3为驱动电机的PWM,这里我们打开通道四
由于要对电机进行测速,所以打开TIM4定时器,设置10ms定时,1 * 7200/72M * 100=10ms
中断配置:先打开TIM2,TIM4中断,再设置定时器优先级,TIM2优先级最高,因为要不断读取编码器数值
配置串口:选择USART1
③生成工程:
在ProjcetManager中,输入Project name ,Toolchain选择MDK_ARM,在Code Genderder,勾选上Generate peripheral initialization as a pair of'c/.h' fles per peripheral,再点击GENERATE CODE
点击魔术棒,勾选上USE MocroLIB
先引入打印头文件
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
重新定义打印函数
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *p)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int fgetc(FILE* f)
{
uint8_t ch;
HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 0 */
打开对应通道
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4); //启动定时器器
HAL_TIM_Base_Start_IT(&htim2); //启动编码器中断
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL); //打开编码器通道
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); //打开TIM3 通道
/* USER CODE END 2 */
可以先给定pwm信号,测试电机转不转。
补充pid知识,本文用的是增量式pid,对应的公式:
其中Kp为比例系数,Ki为积分系数,Kd为微分系数,具体的可以去网上寻找相关资料。
回到工程,新建pid.c文件
#include <pid.h>
int16_t L_Encoder_Speed = 0;
int32_t Position = 0;
int16_t L_Output_Val;
PID L_AddPID;
float speed,count;
void PID_Init(void)
{
L_AddPID.target_val = 60;
L_AddPID.output_val = 0.0;
L_AddPID.Error = 0.0;
L_AddPID.LastError = 0.0;
L_AddPID.integral = 0.0;
L_AddPID.Kp = 80;
L_AddPID.Ki = 4;
L_AddPID.Kd = 1.5;
}
/**
* @brief 速度PID算法实现
* @param actual_val:实际值
* @note 无
* @retval 通过PID计算后的输出
*/
float addPID_realize(PID *pid, float actual_val)
{
/*计算目标值与实际值的误差*/
pid->Error = pid->target_val - actual_val;
/*PID算法实现,照搬公式*/
pid->output_val += pid->Kp * (pid->Error - pid-> LastError) +
pid->Ki * pid->Error +
pid->Kd *(pid->Error -2*pid->LastError+pid->PrevError);
/*误差传递*/
pid-> PrevError = pid->LastError;
pid-> LastError = pid->Error;
/*返回当前实际值*/
if(pid->output_val>Max_Pid_Value)
{
pid->output_val=Max_Pid_Value;
return Max_Pid_Value;
}
else return pid->output_val;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// 4号定时器中断
if(htim == (&htim4))
{
// 设置测量频率为100Hz(10ms)
int rate = 100;
// 获取编码器信号数
L_Encoder_Speed = (int16_t)__HAL_TIM_GET_COUNTER(&htim2);
// 计算10ms的速度
count = ((float)L_Encoder_Speed)/4/11/30;//4:AB向上下沿采样;11:转动一圈11个脉冲;1:30的减速比(不同电机改对应参数)
// 编码器数据清零
__HAL_TIM_SET_COUNTER(&htim2, 0);
// 计数1minute的转速
speed = count * rate* 60; //10ms*100*60=1minute
L_Output_Val=addPID_realize(&L_AddPID,L_Encoder_Speed);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, L_Output_Val);
}
}
对于不同电机,kp,ki,kd的值都应该不同的,大家可以参考网上调参教程
新建pid.h文件
#include <main.h>
#include <tim.h>
#define Max_Pid_Value 10000
typedef struct
{
float target_val; //目标值
float Error; /*第 k 次偏差 */
float LastError; /* Error[-1],第 k-1 次偏差 */
float PrevError; /* Error[-2],第 k-2 次偏差 */
float Kp,Ki,Kd; //比例、积分、微分系数
float integral; //积分值
float output_val; //输出值
}PID;
void PID_Init(void);
extern char Uart3_Rx_Buf[5];
float addPID_realize(PID *pid, float actual_val);
再回到main.c中,在 /* USER CODE END 2 */, 加入PID_Init();
在while 循环中,可加入printf函数,将数据打印到串口助手
接好线,烧录程序,接上电源,打开VOFA+串口,添加波形图控件,Y轴打开全部IO口,启动串口,即可看到波形图生成
中间波形图有波动是由于用手去给它阻力,但看出来还是能及时调整转速的。
END:
由于本人比较小白,PID算法是个很经典的东西,做平衡小车,飞控都会有所涉及到,但对于刚刚开始学习pid算法的我来说简直是一种折磨,实现此工程也花费了几天时间
本文记录是为了自己后期回顾,也可以给大家借鉴学习,有不足的地方欢迎大家留言!