目录
PID控制器参数整定
比例调节:调节作用快,系统一出现偏差,调节器立即将偏差放大输出。
积分调节:输出变化和输入偏差的积分成正比。输出不仅取决于偏差大小,还取决于偏差存在的时间。只要有偏差存在,尽管偏差可能很小,但它存在的时间越长,输出信号就越大。只有消除偏差,输出才停止变化。
微分调节:输出与被调量的变化率成正比。微分调节越大,越能提前响应,但是也会将不必要的偏差放大。
PID控制器参数整定的方法主要分为两大类:理论计算整定方法和工程整定法(经验法)。
理论计算整定方法主要是建立数学模型,然后根据数学模型经过理论的计算来确定最终的控制器参数。这种方法在所有的情况下都是理想的,经过这种方法调出来的参数是不可以直接使用的。
过程整定法主要是依赖于工程中的经验,直接在实际的控制系统的实验中进行。方法简单容易掌握,被广泛使用。
以经验法展开。
试凑法
采样周期的选择:要根据所设计的系统的具体情况,用试凑的方法,在试凑过程中根据各种合理的建议来预选采样周期,多次试凑,选择性能较好的一个作为最后的采样周期。
找整定参数时必须要认真的观察系统的相应情况,根据系统的响应情况来调整参数。
先P,再I,后D。调试时,将PID参数置于影响最小的位置,即P最大、I最大、D最小。
按纯比例系统整定比例系数,使其得到比较理想的调节过程曲线,然后再把比例系数放大1.2倍左右。然后将积分时间从小到大改变,使其得到较好的调节过程曲线。
然后在这个积分时间下重新改变比例系数,再看调节过程曲线有无改善。如有改善,可将原整定的比例系数减小,再改变积分时间,多次循环测试,就能得到合适的比例系数和积分时间。
如果在外界干扰下系统稳定性不好,可把比例系数和积分时间适当增加一点,使系统足够稳定。
将整定好的比例系数和积分时间适当减小,加入微分作用,以得到超调量(曲线最高点和目标线的差值)最小、调节作用时间最短的调节过程。
临界比例法
适用于闭环控制系统。
临界比例法:将调节器置于纯比例的作用下,从大到小逐渐改变调节器的比例系数,并且得到等幅度的震荡过程,此时为临界比例系数。
将调节器的积分置于最大,微分置于0,比例系数适当。
然后将比例逐渐增大,增大到产生等幅现象,并记录等幅时的临界比例系数和两个波峰的时间间隔。
根据记下的比例系数和周期,采用经验公司,计算调节器的参数。
一般调节法
Kp是加快系统响应速度,提高系统的调节精度;Ki用于消除稳态误差;Kd改善系统的稳态性能。
PID调试一般原则(任何参数过大都会造成系统的振荡):
在输出不振荡时,增大比例增益P。
在输出不振荡时,减小积分时间常数Ti。
在输出不振荡时,增大微分时间常数Td。
确定P、I、D参数的一般步骤:
确定比例增益P:
首先去掉PID的积分项和微分项,一般是令Ti=0、Td=0,使PID为纯比例调节。
输入设定为系统允许的最大值的60%~70%,由0逐渐加大比例增益P,直到系统出现振荡。
然后在从此时的比例增益P逐渐减小,直到系统振荡消失。
记录此时的比例增益P,设定PID的比例增益P为当前值的60%~70%。比例增益P调试完成。
确定积分时间常数Ti:
比例增益P确定后,设定一个较大的积分时间常数Ti的初值,然后逐渐减小Ti,直至系统出现振荡。
然后逐渐加大Ti,直到系统振荡消失。
记录此时的Ti,设定PID的积分时间常数Ti为当前值的150%~180%。积分时间常数Ti调试完成。
确定积分时间常数Td:
积分时间常数Td:
一般不用设定,为0即可。若要设定,与前面方法一致,取不振荡时的30%。
系统空载、带载联调,再对PID参数进行微调,直至满足要求。
采样周期选择
采样周期越短,控制的效果越接近于连续,对于大多数算法缩短采样周期可使控制回路性能改善,但采样周期缩短时,频繁的采样会占用较多的计算工作时间,同时也会增加计算负担,而对有些变化缓存的受控对象无需很高的采样频率即可满意地进行跟踪,过多的采样反而没有多少实际意义。
以一个轮子的转动为例,根据奈奎斯特采样定理可知:假设这个轮子以每秒45°来转动,那么每个轴返回原位需要8s(采样周期)。
如果我们在8、16、24s时用相机拍照,则拍到的照片都是在同一位置,会以为是轮子轮子禁止不动。
如果现在减少拍照时间间隔,每4s拍一次,则会发现轮子会变化,但是不能区分旋转方向。
如果每3s拍一次,则会发现轮子会变化,也能区分旋转方向。
这就是Nyquist-Shannon采样定理,我们希望同时看到轮子的旋转和相位变化,采样周期要小于整数周期的1/2,采样频率要大于原始频率的2倍。
野火多功能调试助手--PID
野火PID上位机通过串口与开发板相关联,通过特定协议可以同步上位机和下位机的指令和数据。
野火上位机支持多达10个通道的信号采集,可以同时获取5组PID的调试情况(目标值与实际值为一组)。此外,调试过程中的数据也可以导出为.csv文件,方便数据采集归纳。
在调试过程中,如果需要细致的查看调整曲线,可以通过勾选“停止显示”复选框,在曲线缩放选项中,选择X轴或Y轴进行调整,通过在波形显示界面滚动鼠标滑轮即可完成缩放操作。
STM32解析上位机协议
初始化函数
struct prot_frame_parser_t
{
uint8_t *recv_ptr; // 绑定接收缓冲区
uint16_t r_oft; // 当前缓冲区读指针所在位置
uint16_t w_oft; // 当前缓冲区写指针所在位置
uint16_t frame_len; // 接收到的帧长度
uint16_t found_frame_head; // 标志位,可以判断是否找到帧头
};
static struct prot_frame_parser_t parser;
/* 数据接收缓冲区大小 */
#define PROT_FRAME_LEN_RECV 128
static uint8_t recv_buf[PROT_FRAME_LEN_RECV];
/**
* @brief 初始化接收协议
* @param void
* @return 初始化结果.
*/
int32_t protocol_init(void)
{
memset(&parser, 0, sizeof(struct prot_frame_parser_t));
parser.recv_ptr = recv_buf;
return 0;
}
获取数据包
void USART1_IRQHandler(void)
{
// 一定要通过读DR寄存器,清空接收寄存器,否则又有1字节数据来,则又产生ORE溢出中断。
uint8_t dr = __HAL_UART_FLUSH_DRREGISTER(&husart1);
protocol_data_recv(&dr, 1);
HAL_UART_IRQHandler(&husart1);
}
/**
* @brief 接收数据处理
* @param *data: 要计算的数据的数组.
* @param data_len: 数据的大小
* @return void.
*/
void protocol_data_recv(uint8_t *data, uint16_t data_len)
{
recvbuf_put_data(parser.recv_ptr, PROT_FRAME_LEN_RECV, parser.w_oft, data, data_len); // 接收数据
parser.w_oft = (parser.w_oft + data_len) % PROT_FRAME_LEN_RECV; // 计算写偏移,环形缓冲区
}
/**
* @brief 接收数据写入缓冲区
* @param *buf: 数据缓冲区.
* @param ring_buf_len: 缓冲区大小
* @param w_oft: 写偏移
* @param *data: 需要写入的数据
* @param *data_len: 需要写入数据的长度
* @return void.
*/
static void recvbuf_put_data(uint8_t *buf, uint16_t ring_buf_len, uint16_t w_oft,
uint8_t *data, uint16_t data_len)
{
if ((w_oft + data_len) > ring_buf_len) // 超过缓冲区尾
{
uint16_t data_len_part = ring_buf_len - w_oft; // 缓冲区剩余长度
/* 数据分两段写入缓冲区*/
memcpy(buf + w_oft, data, data_len_part); // 写入缓冲区尾
memcpy(buf, data + data_len_part, data_len - data_len_part); // 写入缓冲区头
}
else
{
memcpy(buf + w_oft, data, data_len); // 数据写入缓冲区
}
}
解析数据包
/**
* @brief 接收的数据处理
* @param void
* @return -1:没有找到一个正确的命令.
*/
int8_t receiving_process(void)
{
uint8_t frame_data[PROT_FRAME_LEN_RECV]; // 要能放下最长的帧
uint16_t frame_len = 0; // 帧长度
uint8_t cmd_type = CMD_NONE; // 命令类型
while (1)
{
// 查询帧类型(命令)
cmd_type = protocol_frame_parse(frame_data, &frame_len);
switch (cmd_type)
{
case CMD_NONE:
{
return -1;
}
case SET_P_I_D_CMD:
{
uint32_t temp0 = COMPOUND_32BIT(&frame_data[13]);
uint32_t temp1 = COMPOUND_32BIT(&frame_data[17]);
uint32_t temp2 = COMPOUND_32BIT(&frame_data[21]);
float p_temp, i_temp, d_temp;
p_temp = *(float *)&temp0;
i_temp = *(float *)&temp1;
d_temp = *(float *)&temp2;
set_p_i_d(p_temp, i_temp, d_temp); // 设置 P I D
}
break;
case SET_TARGET_CMD:
{
int actual_temp = COMPOUND_32BIT(&frame_data[13]); // 得到数据
set_point = (actual_temp); // 设置目标值
}
break;
case START_CMD:
{
// set_motor_enable(); // 启动电机
}
break;
case STOP_CMD:
{
// set_motor_disable(); // 停止电机
}
break;
case RESET_CMD:
{
HAL_NVIC_SystemReset(); // 复位系统
}
break;
case SET_PERIOD_CMD:
{
uint32_t temp = COMPOUND_32BIT(&frame_data[13]); // 周期数
SET_BASIC_TIM_PERIOD(temp); // 设置定时器周期1~1000ms
}
break;
default:
return -1;
}
}
}
将STM32数据同步到上位机
/**
* @brief 设置上位机的值
* @param cmd:命令
* @param ch: 曲线通道
* @param data:参数指针
* @param num:参数个数
* @retval 无
*/
void set_computer_value(uint8_t cmd, uint8_t ch, void *data, uint8_t num)
{
uint8_t sum = 0; // 校验和
num *= 4; // 一个参数 4 个字节
static packet_head_t set_packet;
set_packet.head = FRAME_HEADER; // 包头 0x59485A53
set_packet.len = 0x0B + num; // 包长
set_packet.ch = ch; // 设置通道
set_packet.cmd = cmd; // 设置命令
sum = check_sum(0, (uint8_t *)&set_packet, sizeof(set_packet)); // 计算包头校验和
sum = check_sum(sum, (uint8_t *)data, num); // 计算参数校验和
HAL_UART_Transmit(&husart1, (uint8_t *)&set_packet, sizeof(set_packet), 0xFFFFF); // 发送数据头
HAL_UART_Transmit(&husart1, (uint8_t *)data, num, 0xFFFFF); // 发送参数
HAL_UART_Transmit(&husart1, (uint8_t *)&sum, sizeof(sum), 0xFFFFF); // 发送校验和
}
主体函数
while (1)
{
receiving_process();
}
上位机协议
指令包格式
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | ... | ... | ... | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验的所有数据长度) | 指令(功能码) | 参数1 | ... | 参数2 | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | x | x | x | ... | x | x |
指令汇总
设置上位机通道的目标值
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 目标值(int) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0F | 0x01 | x | x |
设置上位机通道的实际值
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 实际值(int) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0F | 0x02 | x | x |
设置上位机PID值
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 4字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | P值(float) | I值(float) | D值(float) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x17 | 0x03 | x | x | x | x |
设置上位机启动指令(同步上位机的按钮状态)
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0B | 0x04 | x |
设置上位机停止指令(同步上位机的按钮状态)
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0B | 0x05 | x |
设置上位机周期
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 周期(unsigned int) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0F | 0x06 | x | x |
设置下位机的PID值
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 4字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | P值(float) | I值(float) | D值(float) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x17 | 0x10 | x | x | x | x |
设置下位机的目标值
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 目标值(int) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0F | 0x11 | x | x |
设置下位机的启动指令
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0B | 0x12 | x |
设置下位机的停止指令
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0B | 0x13 | x |
设置下位机的复位指令
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0B | 0x14 | x |
设置下位机周期
字节数 | 4字节 | 1字节 | 4字节 | 1字节 | 4字节 | 1字节 |
名称 | 包头(低字节在前) | 通道地址 | 包长度(包头到校验和的所有数据长度) | 指令(功能码) | 周期(unsigned int) | 校验和 |
内容 | 0x59485a53 | CH1(0x01)~CH5(0x05) | 0x0F | 0x15 | x | x |