STM32使用PID调速

STM32使用PID调速

PID原理

image-20230824191607500

PID算法是一种闭环控制系统中常用的算法,它结合了比例(P)、积分(I)和微分(D)三个环节,以实现对系统的控制。它的目的是使

控制系统的输出值尽可能接近预期的目标值。

在PID算法中,控制器通过不断地测量实际输出值和目标值之间的误差,并使用比例、积分和微分部分的控制参数来调整控制系统的输出

值。比例部分根据误差的大小进行控制,其输出与误差成正比。积分部分根据误差的历史累积值进行控制,其输出与误差积分的结果成正

比。微分部分根据误差的变化率进行控制,其输出与误差变化率成正比。

将这三个部分组合起来,就得到了PID算法。PID控制器不断地对系统进行测量和调整,直到实际输出值接近目标值为止。

连续性公式
u ( t ) = K p ∗ e ( t ) + K i ∗ ∫ 0 t e ( t ) d t + k d ∗ d e ( t ) d t u(t)=K_{p}*e(t)+K_{i}*\int_{0}^{t} e(t)dt+k{d}*\frac{de(t)}{dt} u(t)=Kpe(t)+Ki0te(t)dt+kddtde(t)
离散性公式
u ( t ) = K p ∗ e ( t ) + K i ∗ ∑ n = 0 t e ( n ) + k d ∗ [ e ( t ) − e ( t − 1 ) ] u(t)=K_{p}*e(t)+K_{i}*\sum_{n=0}^{t} e(n)+k{d}*[e(t)-e(t-1)] u(t)=Kpe(t)+Kin=0te(n)+kd[e(t)e(t1)]

  • 比例系数Kp:
    比例系数Kp的作用是根据当前误差的大小来调整控制器的输出。Kp越大,控制器对误差的灵敏度越高,系统的响应速度越快,但可能会出现过大的超调。Kp越小,控制器对误差的灵敏度越低,系统的响应速度越慢,但系统的稳定性较好。(快)
  • 积分系数Ki:
    积分系数Ki的作用是根据误差的历史累积值来调整控制器的输出。Ki越大,控制器对误差的累积量越大,系统的稳态误差消除越快,但可能会出现过大的超调。Ki越小,控制器对误差的累积量越小,系统的稳态误差消除越慢,但系统的稳定性较好。(准)
  • 微分系数Kd:
    微分系数Kd的作用是根据误差的变化率来调整控制器的输出。Kd越大,控制器对误差变化率的灵敏度越高,系统的响应速度越快,但可能会出现过大的超调。Kd越小,控制器对误差变化率的灵敏度越低,系统的响应速度越慢,但系统的稳定性较好。(稳)

PID使用

在工程文件中新建

pid.h

//pid.h
#ifndef __BSP_PID_H
#define	__BSP_PID_H
#include "stm32f1xx.h"
#include "usart.h"
#include <stdio.h>
#include <stdlib.h>
#include "tim.h"/*pid*/
typedef struct
{float target_val;float actual_val;float err;float err_last;float err_sum;float Kp,Ki,Kd;
}PID_struct;void PID_Init(PID_struct *pid);
float P_realize(PID_struct * pid, float actual_val);
float PI_realize(PID_struct * pid, float actual_val);
float PID_realize(PID_struct * pid, float actual_val);#endif

结构体中储存pid的参数目标值、当前值、误差、kp、ki、kd等等

pid.c

//pid.c
#include "pid.h"void PID_Init(PID_struct *pid)
{printf("PID_Init begin \n");pid->target_val=1.0;pid->actual_val=0.0;//误差pid->err=0.0;pid->err_last=0.0;pid->err_sum=0.0;//需要自己调节pid->Kp = 120.0;  //快pid->Ki = 5.0;   //准pid->Kd = 0.3;	//稳
}
float P_realize(PID_struct * pid, float actual_val)
{pid->actual_val = actual_val;pid->err = pid->target_val - pid->actual_val;pid->actual_val = pid->Kp * pid->err;return pid->actual_val;
}float PI_realize(PID_struct * pid, float actual_val)
{pid->actual_val = actual_val;pid->err = pid->target_val - pid->actual_val;pid->actual_val = pid->Kp*pid->err + pid->Ki*pid->err_sum;return pid->actual_val;
}float PID_realize(PID_struct * pid, float actual_val)
{pid->actual_val = actual_val;pid->err = pid->target_val - pid->actual_val;pid->err_sum += pid->err;pid->actual_val = pid->Kp*pid->err + pid->Ki*pid->err_sum + pid->Kd*(pid->err-pid->err_last);pid->err_last = pid->err;return pid->actual_val;
}

一共有四个函数分别为PID初始化、P调节、PI调节、PID调节

传入参数为PID结构体,和编码器测的速度

返回值为实际PWM值

使用main.c

#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "motor.h"
#include "pid.h"
#include "oled.h"
/* USER CODE END Includes */short Enc1_cnt = 0;
short Enc2_cnt = 0;
float motor1_speed = 0.00;
float motor2_speed = 0.00;
int PWM_MAX = 1000, PWM_MIN = -1000;
PID_struct motor1_pid;
PID_struct motor2_pid;
int motor1_pwm, motor2_pwm;
char oledBuf[20];void SystemClock_Config(void);int main(void)
{HAL_Init();SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM3_Init();MX_USART1_UART_Init();MX_TIM2_Init();MX_TIM4_Init();/* USER CODE BEGIN 2 */HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);HAL_TIM_Base_Start_IT(&htim2);HAL_TIM_Base_Start_IT(&htim4);//PID初始化PID_Init(&motor1_pid);PID_Init(&motor2_pid);OLED_Init();OLED_ColorTurn(0);OLED_DisplayTurn(0);OLED_Clear();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){motor1_pwm = PID_realize(&motor1_pid, motor1_speed);motor2_pwm = PID_realize(&motor2_pid, motor2_speed);Load_PWM(motor1_pwm, motor2_pwm);Enc1_cnt = -(short)__HAL_TIM_GET_COUNTER(&htim2);Enc2_cnt = (short)__HAL_TIM_GET_COUNTER(&htim4);motor1_speed = (float)Enc1_cnt*100/45/11/4;motor2_speed = (float)Enc2_cnt*100/45/11/4;printf("Enc1_cnt: %d\r\n", Enc1_cnt);printf("Enc2_cnt: %d\r\n", Enc2_cnt);printf("motor1_speed: %.3f\r\n", motor1_speed);printf("motor2_speed: %.3f\r\n", motor2_speed);//OLED显示sprintf(oledBuf, "left_speed :%.3f",motor1_speed);OLED_ShowString(0, 40, (u8*)oledBuf, 12);sprintf(oledBuf, "right_speed:%.3f",motor2_speed);OLED_ShowString(0, 52, (u8*)oledBuf, 12);OLED_Refresh();__HAL_TIM_SET_COUNTER(&htim2, 0);__HAL_TIM_SET_COUNTER(&htim4, 0);HAL_Delay(10);}}

匿名上位机显示波形

匿名上位机下载

匿名上位机通信协议可参考这篇文章匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

使用

新建ano_upper.h

#ifndef STM32_ANO_UPPER_H
#define STM32_ANO_UPPER_H
#include "main.h"
#include "usart.h"//数据拆分宏定义,在发送大于1字节的数据类型时,比如int16、float等,需要把数据拆分成单独字节进行发送#define BYTE0(dwTemp) ( *( (char *)(&dwTemp) ) ) /*!< uint32_t 数据拆分 byte0 */
#define BYTE1(dwTemp) ( *( (char *)(&dwTemp) + 1) ) /*!< uint32_t 数据拆分 byte1 */
#define BYTE2(dwTemp) ( *( (char *)(&dwTemp) + 2) ) /*!< uint32_t 数据拆分 byte2 */
#define BYTE3(dwTemp) ( *( (char *)(&dwTemp) + 3) ) /*!< uint32_t 数据拆分 byte3 */void ANO_DT_Send_F1(uint16_t data1, uint16_t data2, uint16_t data3, uint16_t data4);
void ANO_DT_Send_F2(int16_t data1, int16_t data2, int16_t data3, int16_t data4);
void ANO_DT_Send_F3(int16_t data1, int16_t data2, int32_t data3);
#endif //STM32_ANO_UPPER_H

ano_upper.c

#include "ano_upper.h"
/** 发送数据缓存 */unsigned char data_to_send[50]; //用于绘图/*
* @brief 向上位机发送发送4个uint16_t数据
* @param data1: 发送给上位机显示波形 (可以自己加)
* @return 无
* @note 通过F1帧发送4个uint16类型数据
* @see ANO_DT_Send_F1
*/
void ANO_DT_Send_F1(uint16_t data1, uint16_t data2, uint16_t data3, uint16_t data4)
{unsigned char _cnt=0; //计数值unsigned char i = 0;unsigned char sumcheck = 0; //和校验unsigned char addcheck = 0; //附加和校验data_to_send[_cnt++] = 0xAA; //帧头 0xAAdata_to_send[_cnt++] = 0xFF; //目标地址data_to_send[_cnt++] = 0xF1; //功能码0xF1data_to_send[_cnt++] = 8; //数据长度8个字节//单片机为小端模式-低地址存放低位数据 匿名上位机要求先发低位数据, 所以先发低地址data_to_send[_cnt++]=BYTE0(data1);data_to_send[_cnt++]=BYTE1(data1);data_to_send[_cnt++]=BYTE0(data2);data_to_send[_cnt++]=BYTE1(data2);data_to_send[_cnt++]=BYTE0(data3);data_to_send[_cnt++]=BYTE1(data3);data_to_send[_cnt++]=BYTE0(data4);data_to_send[_cnt++]=BYTE1(data4);for(i=0; i < (data_to_send[3]+4); i++) //数据校验{sumcheck += data_to_send[i]; //从帧头开始,对每一字节进行求和,直到DATA区结束addcheck += sumcheck; //每一字节的求和操作,进行一次sumcheck的累加};data_to_send[_cnt++]=sumcheck;data_to_send[_cnt++]=addcheck;HAL_UART_Transmit(&huart1, data_to_send,_cnt,0xFFFF);
}
/*
* @brief 向上位机发送发送4个int16_t数据
* @param data1: 发送给上位机显示波形 (可以自己加)
* @return 无
* @note 通过F2帧发送4个int16类型数据
* @see ANO_DT_Send_F2
*/
void ANO_DT_Send_F2(int16_t data1, int16_t data2, int16_t data3, int16_t data4)
{unsigned char _cnt=0; //计数值unsigned char i = 0;unsigned char sumcheck = 0; //和校验unsigned char addcheck = 0; //附加和校验data_to_send[_cnt++] = 0xAA; //帧头 0xAAdata_to_send[_cnt++] = 0xFF; //目标地址data_to_send[_cnt++] = 0xF2; //功能码0xF2data_to_send[_cnt++] = 8; //数据长度8个字节//单片机为小端模式-低地址存放低位数据 匿名上位机要求先发低位数据, 所以先发低地址data_to_send[_cnt++]=BYTE0(data1);data_to_send[_cnt++]=BYTE1(data1);data_to_send[_cnt++]=BYTE0(data2);data_to_send[_cnt++]=BYTE1(data2);data_to_send[_cnt++]=BYTE0(data3);data_to_send[_cnt++]=BYTE1(data3);data_to_send[_cnt++]=BYTE0(data4);data_to_send[_cnt++]=BYTE1(data4);for(i=0; i < (data_to_send[3]+4); i++) //数据校验{sumcheck += data_to_send[i]; //从帧头开始,对每一字节进行求和,直到DATA区结束addcheck += sumcheck; //每一字节的求和操作,进行一次sumcheck的累加};data_to_send[_cnt++]=sumcheck;data_to_send[_cnt++]=addcheck;HAL_UART_Transmit(&huart1, data_to_send,_cnt,0xFFFF);
}
/*
* @brief 向上位机发送发送2个int16_t和1个int32_t数据
* @param data1: 发送给上位机显示波形 (可以自己加)
* @return 无
* @note 通过F3帧发送2个int16_t和1个int32_t数据
* @see ANO_DT_Send_F3
*/
void ANO_DT_Send_F3(int16_t data1, int16_t data2, int32_t data3)
{unsigned char _cnt=0; //计数值unsigned char i = 0;unsigned char sumcheck = 0; //和校验unsigned char addcheck = 0; //附加和校验data_to_send[_cnt++] = 0xAA; //帧头 0xAAdata_to_send[_cnt++] = 0xFF; //目标地址data_to_send[_cnt++] = 0xF3; //功能码0xF2data_to_send[_cnt++] = 8; //数据长度8个字节//单片机为小端模式-低地址存放低位数据 匿名上位机要求先发低位数据, 所以先发低地址data_to_send[_cnt++]=BYTE0(data1);data_to_send[_cnt++]=BYTE1(data1);data_to_send[_cnt++]=BYTE0(data2);data_to_send[_cnt++]=BYTE1(data2);data_to_send[_cnt++]=BYTE0(data3);data_to_send[_cnt++]=BYTE1(data3);data_to_send[_cnt++]=BYTE2(data3);for(i=0; i < (data_to_send[3]+4); i++) //数据校验{sumcheck += data_to_send[i]; //从帧头开始,对每一字节进行求和,直到DATA区结束addcheck += sumcheck; //每一字节的求和操作,进行一次sumcheck的累加};data_to_send[_cnt++]=sumcheck;data_to_send[_cnt++]=addcheck;HAL_UART_Transmit(&huart1, data_to_send,_cnt,0xFFFF);
}

main.c

//使用F2帧模式发送4个int16类型数据
ANO_DT_Send_F2(motor1_speed*100, motor2_speed*100, 1.0*100, 1.0*100);

显示
目标值为1.0
pid最终

image-20230824200902554

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/87845.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

类和对象(上)

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

【C#学习笔记】数据类中常用委托及接口——以List<T>为例

文章目录 List\<T\>/LinkedList \<T\>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff09;一些常见&#xff0c;通用的委托和接口ComparisonEnumerator List<T>/LinkedList <T>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff0…

MyBatisPlus实现多租户功能

前言&#xff1a;多租户是一种软件架构技术&#xff0c;在多用户的环境下&#xff0c;共有同一套系统&#xff0c;并且要注意数据之间的隔离性。 一、SaaS多租户简介 1.1、SaaS多租户 SaaS&#xff0c;是Software-as-a-Service的缩写名称&#xff0c;意思为软件即服务&#x…

〔018〕Stable Diffusion 之 批量替换人脸 篇

✨ 目录 &#x1f388; 下载插件&#x1f388; 插件基础使用&#x1f388; 基础使用效果&#x1f388; 批量处理图片&#x1f388; 多人脸部替换 &#x1f388; 下载插件 如果重绘图片的时候&#xff0c;你只想更换人物面部的话&#xff0c;可以参考这篇文章扩展地址&#xff…

温故知新之:代理模式,静态代理和动态代理(JDK动态代理)

0、前言 代理模式可以在不修改被代理对象的基础上&#xff0c;通过扩展代理类&#xff0c;进行一些功能的附加与增强。 1、静态代理 静态代理是一种代理模式的实现方式&#xff0c;它在编译期间就已经确定了代理对象&#xff0c;需要为每一个被代理对象创建一个代理类。静态代…

自定义拖拽功能,上下拖拽改变盒子高度

核心在于监听鼠标的move来改变div的高度&#xff0c;抽成了组件 <template><div ref"container" class"drag"><z-tooltip v-if"isShowIcon" effect"dark" content"格式化" placement"top-start"&…

深入理解ArrayList的动态扩容机制及应用

在java编程中&#xff0c;数据结构起着至关重要的作用&#xff0c;而ArrayList作为一种常用的动态数组&#xff0c;为我们在处理数据时提供了便利。其中&#xff0c;其独特的动态扩容机制更是为其赢得了广泛的应用。我们不管在工作还是面试中&#xff0c;都会遇到ArrayList&…

【腾讯云 TDSQL-C Serverless 产品测评】- 云原生时代的TDSQL-C MySQL数据库技术实践

一、活动介绍&#xff1a; “腾讯云 TDSQL-C 产品测评活动”是由腾讯云联合 CSDN 推出的针对数据库产品测评及产品体验活动&#xff0c;本次活动主要面向 TDSQL-C Serverless版本&#xff0c;初步的产品体验或针对TDSQL-C产品的自动弹性能力、自动启停能力、兼容性、安全、并发…

使用ctcloss训练矩阵生成目标字符串

首先我们需要明确 c t c l o s s ctcloss ctcloss是用来做什么的。比如说要生成的目标字符串长度为 l l l&#xff0c;而这个字符串包含 s s s个字符&#xff0c;字符串允许的最大长度为 L L L&#xff0c;这里认为一个位置是一个时间步&#xff0c;就是一拍&#xff0c;记为 T…

9个至少提升50%效率的VSCODE插件

在开始编码之前&#xff0c;您首先需要让您的工作流程适合您。让它更高效、更漂亮、更可定制。它会节省您大量的时间和精力&#xff0c;因此您将有更多的能力进行编码。 无论您是前端还是后端开发人员还是高级 Java 程序员&#xff0c;都没关系。这篇文章对你们大多数人来说仍然…

【rust/egui】(六)看看template的app.rs:TextEdit

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 TextEdit 文本编辑框 其定义为&#…

干货!证书超实用小Tips

1.CSA 是加拿大标准协会(Canadian Standards Association)的简称&#xff0c;它成立于1919年&#xff0c;是加拿大首家专为制定工业标准的非盈利性机构。在北美市场上销售的电子、电器等产品都需要取得安全方面的认证。 2.EAC认证 是海关联盟国家的认证。海关联盟是由俄罗斯…