<PID调参>VOFA+实现实时PID调参 (附源码)

您所在的位置:网站首页 pid如何调节参数才能输出稳定波形 <PID调参>VOFA+实现实时PID调参 (附源码)

<PID调参>VOFA+实现实时PID调参 (附源码)

2024-07-13 07:38| 来源: 网络整理| 查看: 265

在该篇文章中,本人只做对VOFA+和stm32之间如何实现数据互传,通过VOFA+如何实时修改stm32内的参数,具体如何调PID的过程,日后有空会进行更新,也请关注等待。以下全是本人的个人见解,有更好的想法或者不懂的问题,也可在评论区提问,谢谢大家。

一、什么是VOFA+

VOFA+就是一款串口助手,它不仅能够实现基础的串口数据收发,还能实现数据绘图(包括直方图、FFT图),控件编辑,图像显示等功能。使用VOFA+,可以给我们平常的PID调参等调试带来方便。这是VOFA+官网链接,可以在点击超链接进行下载。

二、如何使用VOFA+

在进入讲解前,可以在VOFA+官方文档中先过一遍基本的界面操作。

1.打开VOFA+的界面

什么都没有,那个密钥也不用去激活,叉掉就行 在这里插入图片描述

2.串口协议配置

这里是串口协议和串口一些配置,配置那些跟着代码来就行,波特率9600,无流控,无校验位,8位数据位,1位停止位,端口号是根据自己stm32与电脑相连的端口号一致,端口号可以在设备管理器中查看到。 在这里插入图片描述 在这里插入图片描述

3.放置控件 a.放置波形图控件

将控件中的第一个拖出来拖到那个大大的窗口中,然后再双击边缘,即我花黑点的地方,就能够放大波形图控件了。当然你不想太大也可以直接就拖动边缘,任意大小。 在这里插入图片描述

b.放置参数控件

这个控件在控件里面的最下面,直接拉三个出来,我这里对应着PID的Kp,Ki,Kd。右击控件对控件进行修改名称和最大值最小值和步长。

在这里插入图片描述

调整完就类似于这样

在这里插入图片描述 这里的绑定命令需要我们对应的在命令的窗口书写命令,这个命令也就是这个控件在做改变数值或者其他事件时会触发的,这个事件对应上图的事件与参数里的几个。

3.命令配置

那命令是什么呢,这里我们可以理解成触发了事件(改变了参数),就执行了命令(发送更改后的参数给stm32),以下就是我对命令的配置。名称即为命令的名称,可以跟对应的控件相同名称。如我这里是Kp,即当我的Kp发生数值改变时,命令就会自动发送一个内容为 #P1=%f! 这里的%f即为你Kp的数值。对应的可以设置Ki,Kd的命令,发送内容分别为 #P2=%f! , #P3=%f! 。这里为什么有123,是因为后面要在stm32中提取该数,然后就可以分别VOFA+发送给stm32是对应哪个需要改变的变量了。而这里的一些#,P,=,! 即为数据包的帧头帧尾,不理解这个概念的可以去看看江科大的串口收发数据包那节网课,本人也是看完那节然后自己码出stm32的代码的。 在这里插入图片描述

命令配置完毕,我们就需要在参数控件右键绑定各个命令,Kp绑定Kp,Ki绑定Ki,Kd绑定Kd。 以上就是VOFA+相关的配置了,在书写代码之前,先保存好相关配置,不然下次打开VOFA+就得重新配置了。 在这里插入图片描述

三、编写代码思路 1.通过数据包对VOFA+传来的数据进行接收。2.除去帧头帧尾将数据包内的传回的数据提取出来。3.将提取出来的数据进行换算,把几个十六进制数换成一个float类型的数。4.将换算后的数赋值给对应的变量。

如:前面设置的命令中的 #P1=%f!,分为了几部分:帧头 #P、变量的辨识id= 1、数据开始提取表识位 =、还有数据本身 %f、和提取结束标志位即帧尾 !

看完江科大串口数据包收发 就能够理解以上的东西了。

以下关于我对数值转换的想法: 假设VOFA+所改变后的数值为12.134,那VOFA+会以ASCII码的形式发送给stm32,所以如果发送了一个感叹号“!”,此时STM32接收到的将会是感叹号的ASCII码,十六进制就是0x21,当我们发送“#”,对应的就是0x23。当我们发送0时,其十六进制就是0x30,对应十进制就是48,所以在对获取到的数值进行计算时,需要将数据减去48,得到其真正的数。 所以,当VOFA+发送#P1=12.134时,stm32接收到的 十六进制数据包就是

0x23 0x50 0x31 0x3D 0x31 0x32 0x2E 0x31 0x33 0x34 0x21 # P 1 = 1 2 . 1 3 4 !

那我们只需要当接收到 0x23,0x50后就知道数据来了,然后将下一位收起来,即0x31,方便后面对该位进行判断,才可知是哪个参数的接收值。判断等号,进入收集数据的状态,将数据存在数组直到受到0x21即感叹号,我们就能退出保存数据

那保存的数据如何处理? 比如上面的12.134 由以上数据包处理后的得到的数据本身={0x31,0x32,0x2E,0x31,0x33,0x34}; 当我们读第一位,即高位时。 我们可以写出 遇到1时: Data =1 遇到2时: Data = 1*10+2 遇到小数点时 不做处理 遇到1时, Data = 12 + 1/10 遇到3时, Data = 12.1 + 3/100 遇到4时, Data = 12.13 + 4/1000

可以发现规律 当遇到小数点前,我们可以用Data = Data*10 + 数据本身[i] 这里可以做判断当没有遇到小数点前,就一直这样i++,循环出小数点前的数。

if(dot_Flag==0) { if(Usart_RxPacket[i]==0x2E)//如果识别到小数点,则将dot_Flag置1 { dot_Flag=1; } else//还没遇到小数点前的运算 { Data = Data*10 + Usart_RxPacket[i]-48; } }

当遇到小数点后 Data = Data + 数据本身[i]/10的-n次方

else//遇到小数点后的运算 { Data = Data + Pow_invert(Usart_RxPacket[i]-48,dot_after_num); dot_after_num++; }

pow_invert函数其实就是一个以10为底的指数函数而已,即X*10的负n次方

float Pow_invert(uint8_t X,uint8_t n)//x除以n次10 { float result=X; while(n--) { result/=10; } return result; }

这样得到的数据,我们只需要返回一开始得到的 标志 即 P后面的数字,和 换算下来的数据本身Data就好了,返回以上的参数可以封装成函数,方便main函数调用。

在pid那边参数获取时,可以在先判断P后面的数字是什么,对应的将Data赋值给p或i或d 。具体对P后面数字的获取我在以下程序源码已给出,有写注解。详情可以在源码中查看。代码内还有对负数的判断方法,其实无非就是判断数据包第一位是否是负号,如果是,就在最后返回的Data乘以-1即可。

四、程序源码

全部复制粘贴就能直接运行了,本人使用的是stm32f103C8T6。

1.串口Usart的c文件 #include "Usart.h" #if 1 #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 void _sys_exit(int x) { x = x; } //重定义fputc函数 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0); USART1->DR=(u8)ch; return ch; } #endif uint8_t Usart_RxData; uint8_t Usart_RxFlag; uint8_t Usart_RxPacket[100];//接受数据包 uint8_t Usart_RxPacket_Len=100;//接受数据包长度 void Usart_Init(void) { RCC_APB2PeriphClockCmd(USART_RCC,ENABLE); RCC_APB2PeriphClockCmd(USART_GPIO_RCC,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//推挽输出,也就是发送口 GPIO_InitStructure.GPIO_Pin=USART_TX; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(USART_GPIO,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入,接收端 GPIO_InitStructure.GPIO_Pin=USART_RX; GPIO_Init(USART_GPIO,&GPIO_InitStructure); USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate=9600;//波特率 USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//流控 USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_InitStructure.USART_Parity=USART_Parity_No;//校验位 USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_Init(USART_X,&USART_InitStructure); //开启串口中断 USART_ITConfig(USART_X,USART_IT_RXNE,ENABLE);//打开接受中断 //配置中断组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=USART_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART_X,ENABLE); USART_ClearFlag(USART_X,USART_FLAG_TC);//清楚串口发送标志位 } void Send_Byte(uint8_t Byte)//发送一个字节 { USART_SendData(USART_X,Byte); while(USART_GetFlagStatus(USART_X,USART_FLAG_TXE)==RESET); } void Send_Array(uint8_t *Array,uint16_t Length)//发送一个数组 { uint16_t i; for(i=0;i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3