1. VOFA+是啥
简单地来说,VOFA+是一个超级串口助手,除了可以实现一般串口助手的串口数据收发,它还可以实现数据绘图(包括直方图、FFT图),控件编辑,图像显示等功能。使用VOFA+,可以给我们平常的PID调参等调试带来方便,还可以自己制作符合自己要求的上位机,为嵌入式开发带来方便。
这个是VOFA+的官网VOFA+ | VOFA+。
2. 如何使用VOFA+调试PID
2.1 VOFA+部分
在正式开始使用VOFA+之前,最好先花十几分钟把官网的文档看一遍,熟悉一下基本操作。
如果只是想要用VOFA+来进行数据绘图,直接使用一个波形图控件就行,但是如果想要把VOFA+当作一个长期使用的调参助手,我们最好设置一下控件。下面是我为调试直流电机的速度环和位置环设置的控件,包含一个波形图,六个参数调节框,两个目标值调节框。
在设置好控件后我们需要对参数调节控件编写命令,这里用速度环的P进行举例。来到命令界面,添加一个新命令并重命名为Spe_P,发送内容为P2=%.2f!,这里的%.2f在实际发送中将被控件中的数值替代(详见官网文档),而感叹号是我自定义的命令结束符。
然后在控件上绑定命令即可
这样每次移动控件就可以发送相应的命令了
重复以上步骤设置完所有命令后VOFA+的部分就完成了。我这里还调整了参数控件的最大最小值,小数点位数,步进值和鼠标弹起后发送而不是边移动边发送等,各位可以按自己的需求调整
编辑完成后记得保存控件和命令,否则软件卡死这些东西就都没了
2.2 STM32部分
在STM32这边,我们要解析VOFA+上位机发过来的数据,并将其数据赋值到相应的变量。
我这里使用的是串口中断进行接收,当然也可以使用DMA进行接收。
首先我们要在CubeMax上设置好串口并开启串口中断,然后在main的while前开启串口中断
uint8_t RxBuffer[1];//串口接收缓冲uint16_t RxLine =0;//指令长度uint8_t DataBuff[200];//指令内容HAL_UART_Receive_IT(&huart2,(uint8_t*)RxBuffer,1);//开启串口中断,我用的是串口2
下一步要编写串口中断函数
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle){if(UartHandle->Instance==USART2)//如果是串口2{
RxLine++;//每接收到一个数据,进入回调数据长度加1
DataBuff[RxLine-1]=RxBuffer[0];//把每次接收到的数据保存到缓存数组if(RxBuffer[0]==0x21)//接收结束标志位,这个数据可以自定义,根据实际需求,这里只做示例使用,不一定是0x21{printf("RXLen=%d\r\n",RxLine);for(int i=0;i<RxLine;i++)printf("UART DataBuff[%d] = %c\r\n",i,DataBuff[i]);USART_PID_Adjust(1);//数据解析和参数赋值函数memset(DataBuff,0,sizeof(DataBuff));//清空缓存数组
RxLine=0;//清空接收长度}
RxBuffer[0]=0;HAL_UART_Receive_IT(&huart2,(uint8_t*)RxBuffer,1);//每接收一个数据,就打开一次串口中断接收,否则只会接收一个数据就停止接收}}
这里有个注意点,我们在VOFA+中设置的是以ASCII码的形式发送,所以如果发送了一个感叹号“!”,此时STM32接收到的将会是感叹号的ASCII码,十六进制下是0x21。如果发送数字0,那么接收到的数据用十进制表示是48,用十六进制表示是0x72。
接下来,我们要将指令中的数据提取出来
/*
* 解析出DataBuff中的数据
* 返回解析得到的数据
*/floatGet_Data(void){uint8_t data_Start_Num =0;// 记录数据位开始的地方uint8_t data_End_Num =0;// 记录数据位结束的地方uint8_t data_Num =0;// 记录数据位数uint8_t minus_Flag =0;// 判断是不是负数float data_return =0;// 解析得到的数据for(uint8_t i=0;i<200;i++)// 查找等号和感叹号的位置{if(DataBuff[i]=='=') data_Start_Num = i +1;// +1是直接定位到数据起始位if(DataBuff[i]=='!'){
data_End_Num = i -1;break;}}if(DataBuff[data_Start_Num]=='-')// 如果是负数{
data_Start_Num +=1;// 后移一位到数据位
minus_Flag =1;// 负数flag}
data_Num = data_End_Num - data_Start_Num +1;if(data_Num ==4)// 数据共4位{
data_return =(DataBuff[data_Start_Num]-48)+(DataBuff[data_Start_Num+2]-48)*0.1f+(DataBuff[data_Start_Num+3]-48)*0.01f;}elseif(data_Num ==5)// 数据共5位{
data_return =(DataBuff[data_Start_Num]-48)*10+(DataBuff[data_Start_Num+1]-48)+(DataBuff[data_Start_Num+3]-48)*0.1f+(DataBuff[data_Start_Num+4]-48)*0.01f;}elseif(data_Num ==6)// 数据共6位{
data_return =(DataBuff[data_Start_Num]-48)*100+(DataBuff[data_Start_Num+1]-48)*10+(DataBuff[data_Start_Num+2]-48)+(DataBuff[data_Start_Num+4]-48)*0.1f+(DataBuff[data_Start_Num+5]-48)*0.01f;}if(minus_Flag ==1) data_return =-data_return;// printf("data=%.2f\r\n",data_return);return data_return;}
最后,将解析得到的数值赋值到PID参数变量中就行啦。
/*
* 根据串口信息进行PID调参
*/voidUSART_PID_Adjust(uint8_t Motor_n){float data_Get =Get_Data();// 存放接收到的数据// printf("data=%.2f\r\n",data_Get);if(Motor_n ==1)//左边电机{if(DataBuff[0]=='P'&& DataBuff[1]=='1')// 位置环P
pid_l_position.kp = data_Get;elseif(DataBuff[0]=='I'&& DataBuff[1]=='1')// 位置环I
pid_l_position.ki = data_Get;elseif(DataBuff[0]=='D'&& DataBuff[1]=='1')// 位置环D
pid_l_position.kd = data_Get;elseif(DataBuff[0]=='P'&& DataBuff[1]=='2')// 速度环P
pid_l_speed.kp = data_Get;elseif(DataBuff[0]=='I'&& DataBuff[1]=='2')// 速度环I
pid_l_speed.ki = data_Get;elseif(DataBuff[0]=='D'&& DataBuff[1]=='2')// 速度环D
pid_l_speed.kd = data_Get;elseif((DataBuff[0]=='S'&& DataBuff[1]=='p')&& DataBuff[2]=='e')//目标速度
L_Target_Speed = data_Get;elseif((DataBuff[0]=='P'&& DataBuff[1]=='o')&& DataBuff[2]=='s')//目标位置
L_Target_Position = data_Get;}elseif(Motor_n ==0)// 右边电机{if(DataBuff[0]=='P'&& DataBuff[1]=='1')// 位置环P
pid_r_position.kp = data_Get;elseif(DataBuff[0]=='I'&& DataBuff[1]=='1')// 位置环I
pid_r_position.ki = data_Get;elseif(DataBuff[0]=='D'&& DataBuff[1]=='1')// 位置环D
pid_r_position.kd = data_Get;elseif(DataBuff[0]=='P'&& DataBuff[1]=='2')// 速度环P
pid_r_speed.kp = data_Get;elseif(DataBuff[0]=='I'&& DataBuff[1]=='2')// 速度环I
pid_r_speed.ki = data_Get;elseif(DataBuff[0]=='D'&& DataBuff[1]=='2')// 速度环D
pid_r_speed.kd = data_Get;elseif((DataBuff[0]=='S'&& DataBuff[1]=='p')&& DataBuff[2]=='e')//目标速度
R_Target_Speed = data_Get;elseif((DataBuff[0]=='P'&& DataBuff[1]=='o')&& DataBuff[2]=='s')//目标位置
R_Target_Position = data_Get;}}
到这里,串口调试的部分就全部完成了,PID部分和定时器部分就不赘述了。
2.2 开始调参
代码写完后,开启串口,就可以实时调整PID各个参数和电机的目标速度了,当PID调节完成后再将参数写进代码中。
这里要注意数据发送时必须严格按照数据引擎的格式,否则软件将不能解析数据进行画图。想要查看格式的花把鼠标移动到数据引擎那里的黑色问号上就行了
这不比原来那种看波形→改参数→烧代码→看波形的流程爽多了
如果在小车和电脑分别插一个蓝牙模块,就能实现小车边跑边调,直接化身远程遥控车,很好玩。
3. 其他
VOFA+简洁好用,但是又有点太简洁了,部分体验并不是很好(比如不能调整控件大小,保存文件不太方便等),所以如果学有余力,推荐自己用QT/PyQT写上位机。
版权归原作者 骇客小狗 所有, 如有侵权,请联系我们删除。