内装设计/seo推广软件排行榜
STM32使用PID调速
PID原理
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)=Kp∗e(t)+Ki∗∫0te(t)dt+kd∗dtde(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)=Kp∗e(t)+Ki∗n=0∑te(n)+kd∗[e(t)−e(t−1)]
- 比例系数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
最终
相关文章:

STM32使用PID调速
STM32使用PID调速 PID原理 PID算法是一种闭环控制系统中常用的算法,它结合了比例(P)、积分(I)和微分(D)三个环节,以实现对系统的控制。它的目的是使 控制系统的输出值尽可能接近预…...

【UE5:CesiumForUnreal】——3DTiles数据属性查询和单体高亮
目录 0.1 效果展示 0.2 实现步骤 1 数据准备 2 属性查询 2.1 射线检测 2.2 获取FeatureID 2.3 属性查询 2.4 属性显示 3 单体高亮 3.1 构建材质参数集 3.2 材质参数设置 3.3 添加Cesium Encode Metadata插件 3.4 从纹理中取出特定FeatureId属性信息 3.5 创建…...

无涯教程-PHP - 返回类型声明
在PHP 7中,引入了一个新函数返回类型声明,返回类型声明指定函数应返回的值的类型,可以声明返回类型的以下类型。 intfloatbooleanstringinterfacesarraycallable 有效返回类型 <?phpdeclare(strict_types1);function returnIntValue(i…...

DOS常见命令
DOS常见命令 DOS是什么如何打开DOScmd常见的命令集合 DOS是什么 DOC命令是我们浏览器中的终端 ,但不同的是我们打开软件的方式 使用的是点击文件图标,点击图标的同时 我们也相当于使用一个命令 只是我们看不见而已 在电脑上操作的时候 通常都是使用命令…...

Qt应用开发(拓展篇)——示波器/图表 QCustomPlot
一、介绍 QCustomPlot是一个用于绘图和数据可视化的Qt C小部件。它没有进一步的依赖关系,提供友好的文档帮助。这个绘图库专注于制作好看的,出版质量的2D绘图,图形和图表,以及为实时可视化应用程序提供高性能。 QCustomPl…...

【精度丢失】后端接口返回的Long类型参数,不同浏览器解析出的结果不一样
1、业务背景 有个同事找我帮他看一个问题,他给前端提供了一个接口。 这个接口是用来反查id的,他这里这个参数正常的返回值应该是 283232039247028226。 但前端反馈他,前端在浏览器(火狐)获取的值是 283232039247028…...

2023年国赛 高教社杯数学建模思路 - 案例:感知机原理剖析及实现
文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法,其…...

java-红黑树
节点内部存储 红黑树规则 或者: 红黑树添加节点规则: 添加节点默认是红色的(效率高) 红黑树示例 注:红黑树增删改查性能都很好...

vue2 vue中的常用指令
一、为什么要学习Vue 1.前端必备技能 2.岗位多,绝大互联网公司都在使用Vue 3.提高开发效率 4.高薪必备技能(Vue2Vue3) 二、什么是Vue 概念:Vue (读音 /vjuː/,类似于 view) 是一套 **构建用户界面 ** 的 渐进式 …...

AI驱动下的智能制造:工业自动化的新纪元
随着人工智能(AI)技术的持续进步,其在工业自动化领域的影响日益显著。作为现代科技的代表,AI不仅为各行业带来了前所未有的商机和技术思路,更在工业自动化领域中引发了一场深刻的变革。本文将深入探讨AI对智能制造的影…...

docker 命令
一、docker命令 1、镜像保存 docker save imageid -o modelzoozl.tar #把镜像保存到本地 docker load -i dockername #把tar包load下来,load成镜像 docker export CONTAINERID/CONTAINERNAME -o modelzoozl.tar #把启动着的镜像导出 docker import modelzo…...

2023年高教社杯数学建模思路 - 复盘:光照强度计算的优化模型
文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米,宽为12米&…...

生成式人工智能的潜在有害影响与未来之路(二)
利润高于隐私:不透明数据收集增加 背景和风险 生成型人工智能工具建立在各种大型、复杂的机器学习模型之上,这些模型需要大量的训练数据才能发挥作用。对于像ChatGPT这样的工具,数据包括从互联网上抓取的文本。对于像Lensa或Stable Diffusi…...

如何自己实现一个丝滑的流程图绘制工具(三)自定义挂载vue组件
背景 bpmn-js是个流程图绘制的工具,但是现在我希望实现的是,绘制的不是节点而是一个vue组件。 保留线的拖拽和连接。 方案 那就说明不是依赖于节点的样式,找到了他有个属性,就是类似覆盖节点的操作。 思路就是用vue组件做遮罩&…...

UNIAPP调用API接口
API:开发者可以通过这些接口与其它程序进行交互,获取所需数据或者执行指定操作。 网络请求 API: UniApp 中内置了网络请求 API,方便调用 uni.request uni.uploadFile uni.request 接口主要用于实现网络请求。GET 和 POST 是使用最普遍的两种…...

理解 Delphi 的类(五) - 认识类的继承
先新建一个 VCL Forms Application 工程, 代码中就已经出现了两个类: 一个是 TForm 类; 一个是 TForm1 类; TForm1 继承于 TForm. TForm 是 TForm1 的父类; TForm1 是 TForm 的子类. unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Contr…...

mybatis概述及搭建
目录 1.概述 2.mybatis搭建 1.创建一个maven项目,添加mybatis、mysql所依赖的jar 2.创建一个数据库表,及对应的java类 3.创建一个mybatis的核心配置文件,配置数据库连接信息,配置sql映射文件 4.创建sql映射文件,…...

DNDC模型---土壤碳储量、温室气体排放、农田减排、土地变化、气候变化中的应用
由于全球变暖、大气中温室气体浓度逐年增加等问题的出现,“双碳”行动特别是碳中和已经在世界范围形成广泛影响。国家领导人在多次重要会议上讲到,要把“双碳”纳入经济社会发展和生态文明建设整体布局。同时,提到要把减污降碳协同增效作为促…...

Android studio 2022.3.1 鼠标移动时不显示快速文档
在使用技术工具的过程中,我们时常会遇到各种各样的问题和挑战。最近,我升级了我的Android Studio到2022.3.1版本,但是在使用过程中,我碰到了一个让我颇为困扰的问题:在鼠标移动到类名或字段上时,原本应该显…...
五度易链最新“产业大数据服务解决方案”亮相,打造数据引擎,构建智慧产业!
快来五度易链官网 点击网址【http://www.wdsk.net/】 看看我们都发布了哪些新功能!!! 自2015年布局产业大数据服务行业以来,“五度易链”作为全国产业大数据服务行业先锋企业,以“让数据引领决策,以智慧驾驭未来”为愿景,肩负“打…...

简述hive环境搭建
文章目录 部署参数配置hive简单命令 部署 Hive的三种部署模式,主要按Metastore 的运行模式进行区分。 在安装Hive之前,要求先预装JDK 8、Hadoop、MySQL ; 1.下载hive,并解压缩到用户主目录下 tar -xzvf apache-hive-2.3.6-bin.t…...

小米AI音箱联网升级折腾记录(解决配网失败+升级失败等问题)
小米AI音箱(一代)联网升级折腾记录 我折腾了半天终于勉强能进入下载升级包这步,算是成功一半吧… 总结就是,网络信号一定要好,需要不停换网找到兼容的网,还需要仔细配置DNS让音响连的上api.mina.mi.com 推荐…...

tensorRT安装
官方指导文档:Installation Guide :: NVIDIA Deep Learning TensorRT Documentation 适配很重要!!!! 需要cuda, cuDNN, tensorRT三者匹配。我的cuda11.3 所以对应的cuDNN和tensorRT下载的是如下版本: cud…...

电脑重装+提升网速
https://www.douyin.com/user/self?modal_id7147216653720341767&showTabfavorite_collectionhttps://www.douyin.com/user/self?modal_id7147216653720341767&showTabfavorite_collection 零封有哈数的主页 - 抖音 (douyin.com)https://www.douyin.com/user/self?…...

Modelica由入门到精通—为什么要学习Modelica语言
1.为什么要学习Modelica语言 本人正在研究Modelica 多领域统一建模仿真语言,特此做学习入门介绍,希望可以帮助需要的小伙伴。 文章目录 1.为什么要学习Modelica语言一、背景二、系统建模与仿真2.1 系统仿真与系统模型2.2 仿真价值与可靠性 三、物理建模…...

opencv 进阶20-随机森林示例
OpenCV中的随机森林是一种强大的机器学习算法,旨在解决分类和回归问题。随机森林使用多个决策树来进行预测,每个决策树都是由随机选择的样本和特征组成的。在分类问题中,随机森林通过投票来确定最终的类别;在回归问题中࿰…...

Spring Boot进阶(58):集成PostgreSQL数据库及实战使用 | 万字长文,超级详细
1. 前言🔥 PostgreSQL是一种广泛使用的开源关系型数据库,具有可靠性高、性能优异、拥有丰富的数据类型和扩展等优点,越来越多的企业和开发者开始使用它来存储和管理数据。而Spring Boot是一种快速开发的框架,可以简化开发过程并提…...

Java | 使用ServerSocket查找TCP可用端口
关注:CodingTechWork 引言 在项目开发中,有一个程序是专门给服务下发tcp端口占用的,但是tcp端口有时候会被其他服务给占用,此时端口就会冲突。本文提供一个工具类进行端口占用判断并返回可用端口。 代码 工具类 Slf4j public …...

【深入浅出C#】章节 9: C#高级主题:LINQ查询和表达式
C#高级主题涉及到更复杂、更灵活的编程概念和技术,能够让开发者更好地应对现代软件开发中的挑战。其中,LINQ查询和表达式是C#高级主题中的一项关键内容,具有以下重要性和优势: 数据处理和操作: 在现代软件中ÿ…...

【Git】git clone --depth 1 浅克隆
问题 PycharmProjects git clone git Cloning into risk-package... remote: Counting objects: 576, done. error: pack-objects died of signal 947/574) error: git upload-pack: git-pack-objects died with error. fatal: git upload-pack: aborting due to possible r…...