智能小车项目(七)通过PID实现给定和实际速度值计算PWM输出

我们先看大脑(上位机nano)
在这里插入图片描述
keybord_ctrl节点发布’cmd_vel’消息消息类型为Twist队列大小为1

pub = rospy.Publisher('cmd_vel', Twist, queue_size=1)
if not stop: pub.publish(twist)

driver_node订阅这个消息 当有消息时cmd_vel_callback回掉函数处理消息调用set_car_motion处理消息

 rospy.init_node("driver_node", anonymous=False) 
    def cmd_vel_callback(self, msg):# 小车运动控制,订阅者回调函数# Car motion control, subscriber callback functionif not isinstance(msg, Twist): return# 下发线速度和角速度# Issue linear vel and angular velvx = msg.linear.xvy = msg.linear.yangular = msg.angular.z# 小车运动控制,vel: ±1, angular: ±5# Trolley motion control,vel=[-1, 1], angular=[-5, 5]# rospy.loginfo("cmd_velx: {}, cmd_vely: {}, cmd_ang: {}".format(vx, vy, angular))self.car.set_car_motion(vx, vy, angular)
    def set_car_motion(self, v_x, v_y, v_z):try:vx_parms = bytearray(struct.pack('h', int(v_x*1000)))vy_parms = bytearray(struct.pack('h', int(v_y*1000)))vz_parms = bytearray(struct.pack('h', int(v_z*1000)))cmd = [self.__HEAD, self.__DEVICE_ID, 0x00, self.FUNC_MOTION, self.__CAR_TYPE, \vx_parms[0], vx_parms[1], vy_parms[0], vy_parms[1], vz_parms[0], vz_parms[1]]cmd[2] = len(cmd) - 1checksum = sum(cmd, self.__COMPLEMENT) & 0xffcmd.append(checksum)self.ser.write(cmd)if self.__debug:print("motion:", cmd)time.sleep(self.__delay_time)except:print('---set_car_motion error!---')pass

再看小脑(下位机32)
下位机接到串口消息进行速度处理Motion_Ctrl

	case FUNC_MOTION:{uint8_t parm = (uint8_t) *(data_buf + 4);int16_t Vx_recv = *(data_buf + 6) << 8 | *(data_buf + 5);int16_t Vy_recv = *(data_buf + 8) << 8 | *(data_buf + 7);int16_t Vz_recv = *(data_buf + 10) << 8 | *(data_buf + 9);uint8_t adjust = parm & 0x80;DEBUG("motion: 0x%02X, %d, %d, %d\n", parm, Vx_recv, Vy_recv, Vz_recv);if (Vx_recv == 0 && Vy_recv == 0 && Vz_recv == 0){Motion_Stop(STOP_BRAKE);}else{Motion_Ctrl(Vx_recv, Vy_recv, Vz_recv, (adjust==0?0:1));}break;}

通过公式计算每个轮子的速度调用Mecanum_Ctrl函数处理速度

// 控制小车运动
void Motion_Ctrl(int16_t V_x, int16_t V_y, int16_t V_z, uint8_t adjust)
{switch (g_car_type){case CAR_MECANUM:{Mecanum_Ctrl(V_x, V_y, V_z, adjust);break;}case CAR_MECANUM_MAX:{if (V_x > CAR_X3_PLUS_MAX_SPEED)  V_x = CAR_X3_PLUS_MAX_SPEED;if (V_x < -CAR_X3_PLUS_MAX_SPEED) V_x = -CAR_X3_PLUS_MAX_SPEED;if (V_y > CAR_X3_PLUS_MAX_SPEED)  V_y = CAR_X3_PLUS_MAX_SPEED;if (V_y < -CAR_X3_PLUS_MAX_SPEED) V_y = -CAR_X3_PLUS_MAX_SPEED;Mecanum_Ctrl(V_x, V_y, V_z, adjust);break;}case CAR_FOURWHEEL:{Fourwheel_Ctrl(V_x, V_y, V_z, adjust);break;}case CAR_ACKERMAN:{Ackerman_Ctrl(V_x, V_y, V_z, adjust);break;}default:break;}
}

调用Motion_Set_Speed设置给定速度到motor_data结构体中

typedef struct _motor_data_t
{float speed_mm_s[4];        // 输入值,编码器计算速度float speed_pwm[4];         // 输出值,PID计算出PWM值int16_t speed_set[4];       // 速度设置值
} motor_data_t;
void Motion_Set_Speed(int16_t speed_m1, int16_t speed_m2, int16_t speed_m3, int16_t speed_m4)
{g_start_ctrl = 1;motor_data.speed_set[0] = speed_m1;motor_data.speed_set[1] = speed_m2;motor_data.speed_set[2] = speed_m3;motor_data.speed_set[3] = speed_m4;for (uint8_t i = 0; i < MAX_MOTOR; i++){PID_Set_Motor_Target(i, motor_data.speed_set[i]*1.0);}
}

将目标速度给入pid_motor[i].target_val中

typedef struct _pid
{float target_val;               //目标值float output_val;               //输出值float pwm_output;        		//PWM输出值float Kp,Ki,Kd;          		//定义比例、积分、微分系数float err;             			//定义偏差值float err_last;          		//定义上一个偏差值float err_next;                 //定义下一个偏差值, 增量式float integral;          		//定义积分值,位置式
} pid_t;
void PID_Set_Motor_Target(uint8_t motor_id, float target)
{if (motor_id > MAX_MOTOR) return;if (motor_id == MAX_MOTOR){for (int i = 0; i < MAX_MOTOR; i++){pid_motor[i].target_val = target;}}else{pid_motor[motor_id].target_val = target;}
}

到这我们完成给定输入

然后我们看实际和给定之间的计算

void Motion_Handle(void)
{Motion_Get_Speed(&car_data);if (g_start_ctrl){Motion_Set_Pwm(motor_data.speed_pwm[0], motor_data.speed_pwm[1], motor_data.speed_pwm[2], motor_data.speed_pwm[3]);}
}

Motion_Get_Speed(&car_data);

首先得到编码器的当前值,编码器的值如何传过来的后面再说
通过该值计算当前各轮速度
通过各轮速度由公式计算当前小车xyz速度

 
// 从编码器读取当前各轮子速度,单位mm/s
void Motion_Get_Speed(car_data_t* car)
{int i = 0;float speed_mm[MAX_MOTOR] = {0};float circle_mm = Motion_Get_Circle_MM();float circle_pulse = Motion_Get_Circle_Pulse();float robot_APB = Motion_Get_APB();Motion_Get_Encoder();// 计算轮子速度,单位mm/s。for (i = 0; i < 4; i++){speed_mm[i] = (g_Encoder_All_Offset[i]) * 100 * circle_mm / circle_pulse;//printf("speed%d : %f",i,speed_mm[i]);}switch (g_car_type){case CAR_MECANUM:{car->Vx = (speed_mm[0] + speed_mm[1] + speed_mm[2] + speed_mm[3]) / 4;car->Vy = -(speed_mm[0] - speed_mm[1] - speed_mm[2] + speed_mm[3]) / 4;car->Vz = -(speed_mm[0] + speed_mm[1] - speed_mm[2] - speed_mm[3]) / 4.0f / robot_APB * 1000;break;}case CAR_MECANUM_MAX:{car->Vx = (speed_mm[0] + speed_mm[1] + speed_mm[2] + speed_mm[3]) / 4;car->Vy = -(speed_mm[0] - speed_mm[1] - speed_mm[2] + speed_mm[3]) / 4;car->Vz = -(speed_mm[0] + speed_mm[1] - speed_mm[2] - speed_mm[3]) / 4.0f / robot_APB * 1000;
//				printf("linear_x:%d\n",car->Vx);
//				printf("linear_y:%d\n",car->Vy);
//				printf("linear_z:%f\n",car->Vz);break;}case CAR_ACKERMAN:{car->Vx = (speed_mm[1] + speed_mm[3]) / 2;car->Vy = Ackerman_Get_Steer_Angle();car->Vz = -(speed_mm[1] - speed_mm[3]) * 1000 / robot_APB;break;}    default:break;}if (g_start_ctrl){for (i = 0; i < MAX_MOTOR; i++){motor_data.speed_mm_s[i] = speed_mm[i];}#if ENABLE_YAW_ADJUST//这个宏关的if (g_yaw_adjust){#if ENABLE_INV_MEMSMecanum_Yaw_Calc(ICM20948_Get_Yaw_Now());#elif ENABLE_MPU9250Mecanum_Yaw_Calc(MPU_Get_Yaw_Now());#endif}#endifPID_Calc_Motor(&motor_data);#if PID_ASSISTANT_EN//这个宏关的if (start_tool()){int32_t speed_send = car->Vx;// int32_t speed_send = (int32_t)speed_m1;set_computer_value(SEND_FACT_CMD, CURVES_CH1, &speed_send, 1);}#endif}
}

调用这个函数计算值
PID_Calc_Motor(&motor_data);

void PID_Calc_Motor(motor_data_t* motor)
{int i;// float pid_out[4] = {0};// for (i = 0; i < MAX_MOTOR; i++)// {//     pid_out[i] = PID_Location_Calc(&pid_motor[i], 0);//     PID_Set_Motor_Target(i, pid_out[i]);// }for (i = 0; i < MAX_MOTOR; i++){motor->speed_pwm[i] = PID_Incre_Calc(&pid_motor[i], motor->speed_mm_s[i]);}
}

调用PID_Incre_Calc这个函数完成PID计算计算出的值给到 motor->speed_pwm中

typedef struct _pid
{float target_val;               //目标值float output_val;               //输出值float pwm_output;        		//PWM输出值float Kp,Ki,Kd;          		//定义比例、积分、微分系数float err;             			//定义偏差值float err_last;          		//定义上两个个偏差值float err_next;                 //定义上一个偏差值, 增量式float integral;          		//定义积分值,位置式
} pid_t;

u(k)=Kp * e(k-1)+Ki *e(k) +Kd *(e(k)-2e(k-1)+e(k-2))+u(k-1);

float PID_Incre_Calc(pid_t *pid, float actual_val)
{/*计算目标值与实际值的误差*/pid->err = pid->target_val - actual_val;/*PID算法实现*/pid->pwm_output += pid->Kp * (pid->err - pid->err_next) + pid->Ki * pid->err + pid->Kd * (pid->err - 2 * pid->err_next + pid->err_last);/*传递误差*/pid->err_last = pid->err_next;pid->err_next = pid->err;/*返回PWM输出值 这里如果忽略死区将MOTOR_IGNORE_PULSE设置为0*/if (pid->pwm_output > (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE))pid->pwm_output = (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE);if (pid->pwm_output < (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE))pid->pwm_output = (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE);return pid->pwm_output;// // 计算偏差// pid->err = pid->target_val - actual_val;// //增量式PI控制器// pid->pwm_output += pid->Kp * (pid->err - pid->err_last) + pid->Ki * pid->err;// if (pid->pwm_output > (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE))//     pid->pwm_output = (MOTOR_MAX_PULSE-MOTOR_IGNORE_PULSE);// if (pid->pwm_output < (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE))//     pid->pwm_output = (MOTOR_IGNORE_PULSE-MOTOR_MAX_PULSE);// pid->err_last = pid->err;// return pid->pwm_output;
}

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

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

相关文章

感染嗜肺军团菌是什么感觉?

记录一下最近生病的一次经历吧&#xff0c;可能加我好友的朋友注意到了&#xff0c;前几天我发了个圈&#xff0c;有热心的朋友还专门私信了我说明了他自己的情况和治疗经验&#xff0c;感谢他们。 ​ 那么关于这次生病的经历&#xff0c;给大家分享一下。 首先&#xff0c;这次…

redis夯实之路-持久化之RDB与AOF详解

数据库 初始化服务器时会根据redisServer的dbnum属性来决定创建多少个数据库&#xff0c;默认为16 使用select切换数据库 客服端状态redisClient结构的db属性记录了当前的目标数据库 RedisDb结构的dict字典保存了数据库的所有键值对&#xff0c;这个字典被称为键空间。 cru…

C++I/O流——(2)预定义格式的输入/输出(第二节)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 含泪播种的人一定能含笑收获&#xff…

【MySQL】MySQL表的约束-空属性/默认值/列属性/zerofill/主键/自增长/唯一键/外键

文章目录 表的约束1.空属性 --null && not null2.默认值 -- default3.列描述4.zerofill5.主键6.自增长7.唯一键8.外键 表的约束 表的约束&#xff1a;表中一定要有各种约束&#xff0c;通过约束&#xff0c;让我们未来插入数据库表中的数据是符合预期的。约束的本质是…

【QT】多层QTreeWidget与QStackedWidget的关联操作

通过点击多层QTreeWidget来控制QStackedWidget中的page页面切换 treeWidget设计 treeWidget设计&#xff1a; // treeWidget设计ui->treeWidget->clear();ui->treeWidget->setColumnCount(1);//第一层QStringList l;l<<"管理系统";QTreeWid…

iPhone“查找”最多可添加32个物品!

对于那些丢三落四的果粉来说&#xff0c;苹果的“查找”功能是一大福音。不管是丢失了iPhone、iPad、Mac、AirPods还是AirTag&#xff0c;都可以通过“查找”功能在地图上追踪设备的位置&#xff0c;甚至是远程锁定或抹掉设备的数据。 那么&#xff0c;iPhone的查找一次能支持添…

LeetCode 38 外观数列

题目描述 外观数列 给定一个正整数 n &#xff0c;输出外观数列的第 n 项。 「外观数列」是一个整数序列&#xff0c;从数字 1 开始&#xff0c;序列中的每一项都是对前一项的描述。 你可以将其视作是由递归公式定义的数字字符串序列&#xff1a; countAndSay(1) "1…

软件测试|解决Github port 443 : Timed out连接超时的问题

前言 GitHub是全球最大的开源代码托管平台之一&#xff0c;许多开发者和团队使用它来管理和协作开源项目。但在当下&#xff0c;我们在clone或者提交代码时会经常遇到"GitHub Port 443: Timed Out"错误&#xff0c;这意味着我们的电脑无法建立与GitHub服务器的安全连…

UniRepLKNet实战:使用UniRepLKNet实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集一些问题 摘要 大核卷积神经网络&#xff08;ConvNets&#xff09;近年来受到广泛关注&#xff0c;但仍存在两个关键问题需要进一步研究。首先&#xff0c;目前的大型卷积神经网络架构大…

Spring Boot - Application Events 同步 VS 异步 发布订阅事件实战

文章目录 PreCode基础工程启动类切入口事件 发布事件同步 Listener异步Listener增加EnableAsync增加 Async 测试 Pre Spring Boot - Application Events 的发布顺序_ApplicationStartingEvent Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEv…

Android Lint的使用

代码检查方式一&#xff1a; Android Studio使用Lint进行代码检查 找到Analyze目录下的Inspect Code检查代码选项点击然后弹出下面这个框框&#xff0c;在这个列表选项中我们可以选择Inspect Code的范围&#xff0c;点击OK 待分析完毕后&#xff0c;我们可以在Inspection栏目中…

安装、运行和控制AI apps在您的计算机上一键式

pinokio 你是否曾为安装、运行和自动化 AI 应用程序和大模型而感到困惑&#xff1f;是否希望有一个简单而强大的工具来满足你的需求&#xff1f;如果是这样&#xff0c;那么 Pinokio 将会是你的理想选择&#xff01;Pinokio 是一款革命性的人工智能浏览器&#xff0c;是一个开…

Nocalhost 为 KubeSphere 提供更强大的云原生开发环境

1 应用商店安装 Nocalhost Server 已集成在 KubeSphere 应用商店&#xff0c;直接访问&#xff1a; 设置应用「名称」&#xff0c;确认应用「版本」和部署「位置」&#xff0c;点击「下一步」&#xff1a; 在「应用设置」标签页&#xff0c;可手动编辑清单文件或直接点击「安装…

HCIP-1

一、网络类型&#xff1a; 点到点 BMA&#xff1a;广播型多路访问 – 在一个MA网络中同时存在广播&#xff08;洪泛&#xff09;机制 NBMA&#xff1a;非广播型多路访问—在一个MA网络中&#xff0c;没有洪泛机制 MA&#xff1a;多路访问 在一个网段内&#xff0c;存在的节…

大模型实战营Day4 XTuner 大模型单卡低成本微调实战

本次讲师是一位从事算法工作的优秀贡献者。 一起来看看吧&#xff01; 本次课程内容主要有&#xff1a; 我将在此整理前三节的内容&#xff0c;第四节放在作业章节进行讲解&#xff1a; 同第三节的建立数据库中所提及到的&#xff0c;如果通用大模型在专用领域表现能力不强&…

普通两样本孟德尔随机化仍能发一区! | 孟德尔随机化周报(1.03-1.09)

欢迎报名2024年孟德尔随机化方法高级班课程&#xff01; 郑老师团队开设的孟德尔随机化高级班2024年1月20-21日开课&#xff0c;欢迎报名 孟德尔随机化,Mendilian Randomization&#xff0c;简写为MR&#xff0c;是一种在流行病学领域应用广泛的一种实验设计方法&#xff0c;利…

【C++ 程序设计入门基础】- 第4节-函数

1、函数 函数是对实现某一功能的代码的模块化封装。 函数的定义&#xff1a; 标准函数&#xff1a; 输入 n 对整数的 a、b &#xff0c;输出它们的和。 #include <iostream> #include <windows.h> using namespace std;int add(int a,int b);//函数原型声明int…

分享一个好用的免费在线扣图网址

具体效果 附地址 https://cutout.aiwave.cc/

dpdk20.11.9 编译arm版本以及在arm 应用中引用dpdk20.11.9

以往19版本的dpdk 都是可以直接用make 的方式进行编译, e.g, make Tx86_64-native-linux-gcc install 为了和客户那边用的DPDK 版本一致, 这次要用dpdk20.11.9, 并且要把之前跑在X86 版本的服务器上的程序跑在ARM 版本上. 目前有两个问题: 1. 编译出arm 版本的dpdk. 2. 把…

程序员副业之AI文库项目(超详细完整全流程)

引言 大家好&#xff0c;我是小黑&#xff0c;今天给大家介绍一个特别简单的项目&#xff0c;用AI辅助咱们来编写各种word&#xff0c;PPT等资料上传到百度文库、原创力、道客巴巴、csdn等平台赚取被动的睡后收益。 但要注意&#xff0c;只是辅助&#xff0c;即便是用AI辅助&…