实例10:四足机器人运动学逆解可视化与实践

news/2024/4/19 21:58:11/文章来源:https://blog.csdn.net/m0_56661101/article/details/129155038

实例10: 四足机器人运动学逆解单腿可视化

实验目的

  1. 了解逆运动学的有无解、有无多解情况。
  2. 了解运动学逆解的求解。
  3. 熟悉逆运动学中求解的几何法和代数法。
  4. 熟悉单腿舵机的简单校准。
  5. 掌握可视化逆向运动学计算结果的方法。

实验要求

  1. 拼装一条mini pupper的腿部。
  2. 运行程序,可视化观察运动学逆解的多解情况和求解方法。
  3. 对单腿舵机进行简单校准。
  4. 观察运动学逆解的硬件运行情况。

实验知识

1.什么是逆向运动学 Inverse Kinematics

正向运动学探究的是已知关节角θi\theta_iθi,求解工具坐标系{H}\{H\}{H}WorldP^{World}PWorldP的问题。
而逆向运动学则是探究已知工具坐标系{H}\{H\}{H}的位置和姿态或WorldP^{World}PWorldP,求解满足要求的θi\theta_iθi的问题。
运动学方程解的有无定义了工作空间,有解则表示机械臂能到达这个目标点,无解则表示机械臂无法到达这个点,这个目标点位于工作空间之外。
基本的逆运动学可以看做是给定操作臂末端执行器的位置和姿态,计算所有可达给定位置和姿态的关节角的问题,可以认为是机器人位姿从笛卡尔空间到关节空间的“定位”映射。

2.什么是封闭解和数值解

逆运动学不像正运动学那么容易,逆运动学是非线性的,难以找到封闭解,有时候无解,有时候有有多解的问题,这种非线性的超越方程组,没有规矩的、统一、通用的解法,解法分为封闭解法和数值解法。
封闭解是由数学公式的推导得出,对于任意自变量均能求出对应的因变量,计算量可能相对较多,精度高。
数值解则是可以由离散查表或者是插值一类的方法去模拟最终情况,计算量相对较小,精度相对较差。
因此,我们需要根据实际情况来考虑逆向运动学的解和解的情况。

2.解存在吗?

在逆向运动学(IK)中,我们可以通过给定点相对于世界坐标系(Frame World)的坐标来解算出机器人的手臂关节应该旋转的角度θ\thetaθ
假如给定的一个位置是在很远的地方,机器人的手臂完全够不着,那么求解是没有意义的,因此我们将会用到工作空间来描述机器人可触达的区域。

工作空间

工作空间是手臂末端所能到达的位置范围。指定的目标点必须在工作空间内,逆向运动学求解才有意义。
为了进一步描述工作空间,可以用以下常见的这两种工作空间的表示:
可达工作空间 Reachable workspace
是手臂能用一种或以上的姿态能够到达的位置范围。可达目标坐标系可以描述这个Frame相对于世界坐标系的位置,而一系列的可达目标坐标系的集合构成了可达工作空间。
灵巧工作空间 Dexterous workspace
是手臂末端用任何姿态都能够到达的位置,条件相当苛刻,比如平面2DOFs的RR机械臂模型中L1=L2的摆臂的圆心,在这个模型中,仅此一点是灵巧工作空间。灵巧工作空间是可达工作空间的子集。

3.是否有多个解?

在求解运动学方程时常常会遇到不只一个解的情况。比如平面中具有三个旋转关节的机器人手臂,对于同一个点PPP,这三个旋转关节可以有不同的位形,在不同的位型下,手臂末端的执行器的可达位置和姿态可以是相同的。
图片:三连杆操作臂多解图

解的选取

对于解的选取有一些基本原则:

  1. 速度最快
  2. 能耗最低
  3. 避开障碍物
  4. 在关节允许活动的范围限制内

4.如何求解?

求解操作臂运动学方程是非线性的问题,非线性方程组没有通用的求解算法,算法需要针对机器人手臂的模型来制定。如果某一算法可以解出与已知位姿相关的全部关节变量,那么这个机器人手臂就是可解的。
Frameobject{Frame_{object}}FrameobjectFrameWorld{Frame_{World}}FrameWorld的变换矩阵oWT^W_oToWT中的转动部分和平移部分可以提取出含未知数的16个数字。
其中的旋转矩阵被xyz相互垂直、xyz为单位向量这六个条件限制到只有三个自由度,其中的位置矢量分量的三个方程有三个自由度,共有6个限制条件,6个自由度,这些方程为非线性超越方程,求解不易。
对于六旋转关节的机械臂,存在解析解(封闭解)的充分条件是相邻的三关节的转轴交于一点。

60T=[60R3x30P6ORG3x10001]4x4=[X^6⋅X^0Y^6⋅X^0Z^6⋅X^060PXorgX^6⋅Y^0Y^6⋅Y^0Z^6⋅Y^060PYorgX^6⋅Z^0Y^6⋅Z^0Z^6⋅Z^060PZorg0001]^0_6T = \left[ \begin{matrix} ^0_6R_{3x3} &^0P_{6{\kern 2pt}ORG{\kern 2pt}3x1}\\ 0{\kern 3pt}0{\kern 3pt}0&1 \end{matrix} \right]_{4x4}= \left[ \begin{matrix} \hat X_6\cdot \hat X_0& \hat Y_6\cdot \hat X_0 & \hat Z_6\cdot \hat X_0 & ^0_6P_{Xorg}\\ \hat X_6\cdot \hat Y_0& \hat Y_6\cdot \hat Y_0&\hat Z_6\cdot \hat Y_0 &^0_6P_{Yorg}\\ \hat X_6\cdot \hat Z_0& \hat Y_6\cdot \hat Z_0&\hat Z_6\cdot \hat Z_0 &^0_6P_{Zorg}\\ 0&0&0&1 \end{matrix} \right] 60T=[60R3x30000P6ORG3x11]4x4=X^6X^0X^6Y^0X^6Z^00Y^6X^0Y^6Y^0Y^6Z^00Z^6X^0Z^6Y^0Z^6Z^0060PXorg60PYorg60PZorg1
对于基于解析形式的解法,常见的求解方法有几何法和代数法。两种方法相似,求解过程不同。

几何法

几何法求解机械臂的逆运动学问题时,常常需要将空间几何参数转化为平面几何的问题。在αi=0或−+90°\alpha_i=0 或 ^+_-90°αi=0+90°时几何法会非常容易,应用平面几何常见的公式及角度转换即可求出θi\theta_iθi的值。
在这里插入图片描述

x2+y2=l12+l22−2l1l2(π−θ2)(余弦定理)x^2+y^2=l^2_1+l^2_2-2l_1l_2(\pi-\theta_2) \tag{余弦定理} x2+y2=l12+l222l1l2(πθ2)(余弦定理)
Cosθ2=x2+y2−l12−l222l1l2(变形1)Cos\theta_2={x^2+y^2-l^2_1-l^2_2\over 2l_1l_2} \tag{变形1} Cosθ2=2l1l2x2+y2l12l22(变形1)
Cosψ=(x2+y2)+l12−l222l1x2+y2(变形2)Cos\psi={(x^2+y^2)+l^2_1-l^2_2\over 2l_1\sqrt{x^2+y^2}} \tag{变形2} Cosψ=2l1x2+y2x2+y2+l12l22(变形2)

θ1={atan2(y,x)+ψθ2<0atan2(y,x)−ψθ2>0\theta_1= \begin{cases} atan2(y,x)+\psi& \theta_2<0\\ atan2(y,x)-\psi& \theta_2>0\\ \end{cases} θ1={atan2(y,x)+ψatan2(y,x)ψθ2<0θ2>0
在计算完θ2\theta_2θ2θ1\theta_1θ1后,根据图的几何角度关系,又可算得θ3\theta_3θ3,即成功反解运动学的各θn\theta_nθn

math.atan2()方法

math.atan2()方法是双变量反正切公式,可以计算给定y,x值的反正切值,也就是以弧度形式表达的该段终点与起点连线斜率线的一个角度值。
atan2()优于atan(),因为可以计算x2-x1=0的情况。
参考链接:Python math.atan2(y,x)
计算空间中缺少的自由度

代数法

应用连杆参数(αi−1\alpha_{i-1}αi1,ai−1a_{i-1}ai1,θi\theta_{i}θi,did_{i}di),通过运动学正解(FK)可以求得机械臂的运动学方程,表现形式为变换矩阵30T^0_3T30T。因此,目标点的位置是由手臂末端坐标系相对基坐标系来定的,当研究对象为平面机械臂时,只需要知道x,y,ϕ\phiϕ即可确定目标点位置。
ϕ\phiϕ是 末端杆在平面内的姿态角
将已知的30T^0_3T30T与新建立的objectworldT^{world}_{object}TobjectworldT取等,即可获得对应位置的值相等。
[c123−s1230.0l1c1+l2c12s123c1230.0l1s1+l2s120.00.01.00.00001]=[cϕ−sϕ0.0xsϕcϕ0.0y0.00.01.00.00001]\left[ \begin{matrix} c_{123} & -s_{123} & 0.0&l_1c_1+l_2c_{12} \\ s_{123} & c_{123} & 0.0&l_1s_1+l_2s_{12} \\ 0.0 & 0.0 & 1.0 &0.0 \\ 0 & 0 & 0 &1 \\ \end{matrix} \right]= \left[ \begin{matrix} c_{\phi} & -s_{\phi} & 0.0&x \\ s_{\phi} & c_{\phi} & 0.0&y \\ 0.0 & 0.0 & 1.0 &0.0 \\ 0 & 0 & 0 &1 \\ \end{matrix} \right] c123s1230.00s123c1230.000.00.01.00l1c1+l2c12l1s1+l2s120.01=cϕsϕ0.00sϕcϕ0.000.00.01.00xy0.01
利用三角函数和角公式
Sin1−+2=Sin1Cos2−+Cos1Sin2Sin_{1 {^+_-}2}=Sin_1Cos_2 {^+_-} Cos_1Sin_2 Sin1+2=Sin1Cos2+Cos1Sin2
Cos1−+2=Cos1Cos2+−Sin1Sin2Cos_{1 {^+_-}2}=Cos_1Cos_2 {^-_+}Sin_1Sin_2 Cos1+2=Cos1Cos2+Sin1Sin2
可得
Cosθ2=x2+y2−l12−l222l1l2Cos\theta_2={x^2+y^2-l^2_1-l^2_2\over 2l_1l_2} Cosθ2=2l1l2x2+y2l12l22
此式在1≥Cosθ2≥−11\geq Cos\theta_2\geq-11Cosθ21时有解

假设目标点在工作空间内,又有
Sinθ2=−+1−c2Sin\theta_2= {^+_-}\sqrt {1-c^2} Sinθ2=+1c2
应用几何法中提到的math.Atan2()求解θ2\theta_2θ2,利用θ2\theta_2θ2再去对其他θn\theta_nθn求解,具体方法参考教材,本处仅作代数法引入。
通俗的来说,就是确认θn\theta_nθnSinθnSin\theta_nSinθnCosθnCos\theta_nCosθn,再利用双变量反正切公式math.Atan2()求θn\theta_nθn

实验步骤

1.逆运动学的多解与求解

运行程序,观察运动学逆解的多解情况,观察程序中运动学逆解的求解方法。

sudo python rr_IK.py
# 示例值: 3 7
#!/usr/bin/python
# coding:utf-8
# rr_IK.py
# 逆向运动学IK
# mini pupper的简化单腿,可视作同一平面的RR类机械臂,可视化该机械臂,由给定末端位置计算转轴角度
import matplotlib.pyplot as plt  # 引入matplotlib
import numpy as np  # 引入numpy
from math import degrees, radians, sin, cos# 几何法:端点坐标转关节角
def position_2_theta(x, y, l1, l2):"""运动学逆解 将输入的端点坐标转化为对应的关节角:param x: p点坐标x值:param y: p点坐标y值:param l1: 大臂长:param l2: 小臂长:return: 关节角1值1 关节角1值2 关节角2值1 关节角2值1"""cos2 = (x ** 2 + y ** 2 - l1 ** 2 - l2 ** 2) / (2 * l1 * l2)# print(cos2)sin2_1 = np.sqrt(1 - cos2 ** 2)sin2_2 = -sin2_1# print(sin2_1)# print("sin2有两值,分别为sin2_1=%f, sin2_2=%f" % (sin2_1, sin2_2))  # 若考虑关节情况也可只取一个正值theta2_1 = np.arctan2(sin2_1, cos2)theta2_2 = np.arctan2(sin2_2, cos2)phi_1 = np.arctan2(l2 * sin2_1, l1 + l2 * cos2)phi_2 = np.arctan2(l2 * sin2_2, l1 + l2 * cos2)theta1_1 = np.arctan2(y, x) - phi_1theta1_2 = np.arctan2(y, x) - phi_2# print(degrees(theta1_1), degrees(theta1_2), degrees(theta2_1), degrees(theta2_2))return theta1_1, theta1_2, theta2_1, theta2_2def preprocess_drawing_data(theta1, theta2, l1, l2):"""处理角度数据,转化为matplotlib适应的绘图格式:param theta1: 角度数据1:param theta2: 角度数据2:param l1: 杆件长1:param l2: 杆件长2:return: 绘图数据x坐标list和对应的y坐标list"""xs = [0]ys = [0]# 分别算出x1 y1和x2 y2x1 = l1 * cos(theta1)y1 = l1 * sin(theta1)x2 = x1 + l2 * cos(theta1 + theta2)y2 = y1 + l2 * sin(theta1 + theta2)xs.append(x1)xs.append(x2)ys.append(y1)ys.append(y2)return xs, ysdef annotate_angle(x0, y0, rad1, rad2, name, inverse=False):"""为两条直线绘制角度:param x0: 圆心x坐标:param y0: 圆心x坐标:param rad1: 起始角:param rad2: 终止角:param name: 角名:param inverse: 用于解决点1的重叠问题:return: 无"""theta = np.linspace(rad1, rad2, 100)  # 0~radr = 0.2  # circle radiusx1 = r * np.cos(theta) + x0y1 = r * np.sin(theta) + y0plt.plot(x1, y1, color='red')plt.scatter(x0, y0, color='blue')degree = degrees((rad2 - rad1))if inverse:plt.annotate("%s=%.1f°" % (name, degree), [x0, y0], [x0 - r / 1.5, y0 - r / 1.5])else:plt.annotate("%s=%.1f°" % (name, degree), [x0, y0], [x0 + r / 1.5, y0 + r / 1.5])# 关节信息
# 大臂长度:5 cm 小臂长度:7.5 cm
link_length = [5, 7.5]  # in cm
# 输入末端位置
position_pre = input("请输入末端的x坐标和y坐标,以空格隔开:")
position = [float(n) for n in position_pre.split()]
print(position)# 计算并预处理绘图数据
joints_angles = position_2_theta(position[0], position[1], link_length[0], link_length[1])
# print(joints_angles)
figure1 = preprocess_drawing_data(joints_angles[0], joints_angles[2], link_length[0], link_length[1])
figure2 = preprocess_drawing_data(joints_angles[1], joints_angles[3], link_length[0], link_length[1])
# print(figure1)
# print(figure2)# 绘图
fig, ax = plt.subplots()  # 建立图像
plt.axis("equal")
ax.grid()
plt.plot(figure1[0], figure1[1], color='black', label='method 1')
plt.scatter(figure1[0], figure1[1], color='black')
plt.plot(figure2[0], figure2[1], color='red', label='method 2')
plt.scatter(figure2[0], figure2[1], color='blue')
ax.set(xlabel='X', ylabel='Y', title='mini pupper IK RR model')
plt.legend()
# 标注
annotate_angle(figure1[0][0], figure1[1][0], 0, joints_angles[0], "theta1_1")
annotate_angle(figure1[0][1], figure1[1][1], joints_angles[0], joints_angles[2]+joints_angles[0], "theta2_1")
annotate_angle(figure2[0][0], figure2[1][0], 0, joints_angles[1], "theta1_2", inverse=True)
annotate_angle(figure2[0][1], figure2[1][1], joints_angles[1], joints_angles[3]+joints_angles[1], "theta2_2")
plt.annotate("P(%d, %d)" % (position[0], position[1]), [figure1[0][2], figure1[1][2]],[figure1[0][2] + 0.1, figure1[1][2] + 0.1])
plt.tight_layout()
plt.show()

2.逆运动学可视化

观察程序,通过圆轨迹的运动学逆解来观察mini pupper腿部的运动

#!/usr/bin/python
# coding:utf-8
# rr_IK_circle.py
# 逆向运动学IK
# mini pupper的简化单腿,可视作同一平面的RR类机械臂,可视化四足机器人逆运动学画圈
import matplotlib.pyplot as plt  # 引入matplotlib
import numpy as np  # 引入numpy
from math import degrees, radians, sin, cos
import matplotlib.animation as animation# 几何法:端点坐标转关节角
def position_2_theta(x, y, l1, l2):"""运动学逆解 将输入的端点坐标转化为对应的关节角:param x: p点坐标x值:param y: p点坐标y值:param l1: 大臂长:param l2: 小臂长:return: 关节角1值1 关节角1值2 关节角2值1 关节角2值1"""cos2 = (x ** 2 + y ** 2 - l1 ** 2 - l2 ** 2) / (2 * l1 * l2)sin2_1 = np.sqrt(1 - cos2 ** 2)sin2_2 = -sin2_1theta2_1 = np.arctan2(sin2_1, cos2)theta2_2 = np.arctan2(sin2_2, cos2)phi_1 = np.arctan2(l2 * sin2_1, l1 + l2 * cos2)phi_2 = np.arctan2(l2 * sin2_2, l1 + l2 * cos2)theta1_1 = np.arctan2(y, x) - phi_1theta1_2 = np.arctan2(y, x) - phi_2return theta1_1, theta1_2, theta2_1, theta2_2def preprocess_drawing_data(theta1, theta2, l1, l2):"""处理角度数据,转化为matplotlib适应的绘图格式:param theta1: 角度数据1:param theta2: 角度数据2:param l1: 杆件长1:param l2: 杆件长2:return: 绘图数据x坐标list和对应的y坐标list"""xs = [0]ys = [0]# 分别算出x1 y1和x2 y2x1 = l1 * cos(theta1)y1 = l1 * sin(theta1)x2 = x1 + l2 * cos(theta1 + theta2)y2 = y1 + l2 * sin(theta1 + theta2)xs.append(x1)xs.append(x2)ys.append(y1)ys.append(y2)return xs, ysdef animate_plot(n):# 生成圆轨迹circle_point = [2.696152422706633, -7.330127018922193]  # 圆周运动的圆心position = [0, 0]history_position_x = [0]history_position_y = [0]circle_r = 2theta = n * np.pi / 100position[0] = circle_point[0] + circle_r * np.cos(theta)position[1] = circle_point[1] + circle_r * np.sin(theta)# 计算并预处理绘图数据joints_angles = position_2_theta(position[0], position[1], link_length[0], link_length[1])figure1 = preprocess_drawing_data(joints_angles[0], joints_angles[2], link_length[0], link_length[1])# 轨迹追踪for i in range(0, (n % 200)+1):history_theta = ((n % 200) + 1 - i) * np.pi / 100history_position_x.append(circle_point[0] + circle_r * np.cos(history_theta))history_position_y.append(circle_point[1] + circle_r * np.sin(history_theta))# 画图p = plt.plot(figure1[0], figure1[1], 'o-', lw=2, color='black')p += plt.plot(history_position_x, history_position_y, '--', color='blue', lw=1)return p# 关节信息
# 大臂长度:5 cm 小臂长度:7.5 cm
link_length = [5, 7.5]  # in cm# matplotlib可视化部分
fig, ax = plt.subplots()  # 建立图像
plt.axis("equal")
plt.grid()
ax.set(xlabel='X', ylabel='Y', title='mini pupper IK RR model Circle Plot')
ani = animation.FuncAnimation(fig, animate_plot, interval=10, blit=True)
plt.show()

3.校准舵机

将组装好的单腿各舵机线材,按照程序中提示的接线接入,对舵机2与舵机3进行回零校准。

#!/usr/bin/python
# coding:utf-8
# servo_calibrate.py
# 默认舵机全部回零,随后等待输入角度import RPi.GPIO as GPIO
import timedef servo_map(before_value, before_range_min, before_range_max, after_range_min, after_range_max):"""功能:将某个范围的值映射为另一个范围的值参数:原范围某值,原范围最小值,原范围最大值,变换后范围最小值,变换后范围最大值返回:变换后范围对应某值"""percent = (before_value - before_range_min) / (before_range_max - before_range_min)after_value = after_range_min + percent * (after_range_max - after_range_min)return after_valuesignal_ports = input("键入各舵机的信号端口号,以空格隔开,无输入按回车默认信号口为:32 33 35\n请输入:") or "32 33 35"
signal_ports = [int(n) for n in signal_ports.split()]
for i in range(0, len(signal_ports)):print("舵机%d对应的口为%d" % (i+1, signal_ports[i]))GPIO.setmode(GPIO.BOARD)  # 初始化GPIO引脚编码方式
servo = [0, 0, 0]
servo_SIG = signal_ports  # PWM信号端
servo_VCC = [2, 4, 1]  # VCC端
servo_GND = [30, 34, 39]  # GND端
servo_freq = 50  # PWM频率
servo_width_min = 2.5  # 工作脉宽最小值
servo_width_max = 12.5  # 工作脉宽最大值
GPIO.setmode(GPIO.BOARD)  # 初始化GPIO引脚编码方式
for i in range(0, len(servo_SIG)):GPIO.setup(servo_SIG[i], GPIO.OUT)servo[i] = GPIO.PWM(servo_SIG[i], servo_freq)servo[i].start(0)servo[i].ChangeDutyCycle((servo_width_min + servo_width_max) / 2)  # 回归舵机中位
print("初始化回零完成,两秒后等待输入")
time.sleep(2)# 为舵机指定位置
try:  # try和except为固定搭配,用于捕捉执行过程中,用户是否按下ctrl+C终止程序while 1:angles = input("如果你需要改变舵机角度,请依次为不同舵机输入0°-180°的角度值:\n")angles = [int(n) for n in angles.split()]for i in range(0, len(angles)):dc_trans = servo_map(angles[i], 0, 180, servo_width_min, servo_width_max)servo[i].ChangeDutyCycle(dc_trans)print("舵机%d已转动到%d°" % (i+1, angles[i]))
except KeyboardInterrupt:pass
for i in range(0, len(servo_SIG)):servo[i].stop()  # 停止pwm
GPIO.cleanup()  # 清理GPIO引脚

4.观察运动学逆解的实际运行

运动学逆解的实际运行会受到非常多因素的干扰,例如校准情况、杆间的测量误差、信号线材的传输波动、树莓派本身的计算能力。
值得一提的是,千机千面,舵机的校准每个人遇到的情况不同,对于单腿的舵机,在3中提到的校准程序只能帮助你发现简单的安装错误,如果需要实际校准,需要运行整机的可视化校准代码。

如果你希望可视化硬件的运行情况,虽然mini pupper并没有设置足端反馈,但你也可以将代码中的绘图部分注释恢复,这会使得舵机运动和电脑端绘图同步进行,这对算力要求较高,可能会出现卡顿。

#!/usr/bin/python
# coding:utf-8
# rr_IK_circle_synchronous.py
# 运动学逆解画圆,同步控制端图像显示和硬件运动
import matplotlib.pyplot as plt  # 引入matplotlib
import numpy as np  # 引入numpy
from math import degrees, radians, sin, cos
import matplotlib.animation as animation
import time
import RPi.GPIO as GPIO# 几何法:端点坐标转关节角
def position_2_theta(x, y, l1, l2):"""运动学逆解 将输入的端点坐标转化为对应的关节角:param x: p点坐标x值:param y: p点坐标y值:param l1: 大臂长:param l2: 小臂长:return: 关节角1值1 关节角1值2 关节角2值1 关节角2值1"""cos2 = (x ** 2 + y ** 2 - l1 ** 2 - l2 ** 2) / (2 * l1 * l2)sin2_1 = np.sqrt(1 - cos2 ** 2)sin2_2 = -sin2_1theta2_1 = np.arctan2(sin2_1, cos2)theta2_2 = np.arctan2(sin2_2, cos2)phi_1 = np.arctan2(l2 * sin2_1, l1 + l2 * cos2)phi_2 = np.arctan2(l2 * sin2_2, l1 + l2 * cos2)theta1_1 = np.arctan2(y, x) - phi_1theta1_2 = np.arctan2(y, x) - phi_2return theta1_1, theta1_2, theta2_1, theta2_2def servo_map(before_value, before_range_min, before_range_max, after_range_min, after_range_max):"""功能:将某个范围的值映射为另一个范围的值参数:原范围某值,原范围最小值,原范围最大值,变换后范围最小值,变换后范围最大值返回:变换后范围对应某值"""percent = (before_value - before_range_min) / (before_range_max - before_range_min)after_value = after_range_min + percent * (after_range_max - after_range_min)return after_valuedef theta_to_servo_degree(theta, servo_number, relation_list, config_calibration_value=None):"""将杆件的角度转化为舵机角度:param theta: 弧度制 杆件角度:param servo_number: 舵机号:param relation_list: 舵机关系映射表:param config_calibration_value::return:舵机角 in 角度制"""if config_calibration_value is None:config_calibration_value = [0, 0, 0]theta = degrees(theta)servo_degree = 0if servo_number == 1:print("servo1")servo_degree = 0  # 此处需要根据舵机1修改elif servo_number == 2:print("servo2")servo_degree = theta + relation_list[1]+config_calibration_value[1]elif servo_number == 3:print("servo3")servo_degree = theta + relation_list[2]+config_calibration_value[2]else:print("ERROR:theta_to_servo_degree")servo_degree = 0return servo_degreedef servo_control(servo_number, degree):"""通过角度值控制电机输出对应的角度:return:"""dc_trans = servo_map(degree, 0, 180, servo_width_min, servo_width_max)servo[servo_number-1].ChangeDutyCycle(dc_trans)print("舵机%d已转动到%d°处" % (servo_number, degree))def circle_point_generate(center_point, radius, count):"""输入圆心[x0,y0],半径r,及计数c,返还圆周上单个点的坐标[x,y]count范围:0~360:param center_point: 圆心[x0,y0]:param radius: 半径:param count: 计数 范围:0~360:return: 圆周上单个点的坐标[x,y]"""theta = (count%360)/360 * 2 * np.piposition = [0, 0]position[0] = center_point[0] + radius * np.cos(theta)position[1] = center_point[1] + radius * np.sin(theta)return positiondef preprocess_drawing_data(theta1, theta2, l1, l2):"""处理角度数据,转化为matplotlib适应的绘图格式:param theta1: 角度数据1:param theta2: 角度数据2:param l1: 杆件长1:param l2: 杆件长2:return: 绘图数据x坐标list和对应的y坐标list"""xs = [0]ys = [0]# 分别算出x1 y1和x2 y2x1 = l1 * cos(theta1)y1 = l1 * sin(theta1)x2 = x1 + l2 * cos(theta1 + theta2)y2 = y1 + l2 * sin(theta1 + theta2)xs.append(x1)xs.append(x2)ys.append(y1)ys.append(y2)return xs, ysdef animate_plot(n):# 圆轨迹生成position = circle_point_generate(center_point, radius, n)# 运动学逆解joints_angles = position_2_theta(position[0], position[1], link_length[0], link_length[1])# 逆解值转绘图数据figure1 = preprocess_drawing_data(joints_angles[0], joints_angles[2], link_length[0], link_length[1])# 杆件角度转舵机角度servo_degree[0] = theta_to_servo_degree(joints_angles[0], 2, config_degree_relation_list)servo_degree[1] = theta_to_servo_degree(joints_angles[2], 3, config_degree_relation_list)# 硬件舵机运动同步servo_control(1, servo_degree[0])servo_control(2, servo_degree[1])# # 画图# p = plt.plot(figure1[0], figure1[1],'o-',lw=2, color='black')# return p# 配置及初始化
center_point = [1.767767, -8.838835]  # 圆周运动的圆心
radius = 2  # 圆半径
link_length = [5, 7.5]  # 杆件长度 in cm
config_degree_relation_list=[+0, +225, +0]
servo = [0, 0, 0]
servo_degree = [0, 0, 0]
servo_SIG = [32, 33]  # PWM信号端
servo_VCC = [2, 4, 1]  # VCC端
servo_GND = [30, 34, 39]  # GND端
servo_freq = 50  # PWM频率
servo_width_min = 2.5  # 工作脉宽最小值
servo_width_max = 12.5  # 工作脉宽最大值
GPIO.setmode(GPIO.BOARD)  # 初始化GPIO引脚编码方式
for i in range(0, len(servo_SIG)):GPIO.setup(servo_SIG[i], GPIO.OUT)servo[i] = GPIO.PWM(servo_SIG[i], servo_freq)servo[i].start(0)servo[i].ChangeDutyCycle((servo_width_min + servo_width_max) / 2)  # 回归舵机中位
print("初始化回零完成,两秒后等待操作")
time.sleep(2)# # matplotlib可视化部分
# fig, ax = plt.subplots()  # 建立图像
# plt.axis("equal")
# plt.grid()
# ax.set(xlabel='X', ylabel='Y', title='mini pupper IK RR model Circle Plot')
# ani = animation.FuncAnimation(fig, animate_plot, interval=1, blit=True)
# plt.show()
n = 0
try:    # try和except为固定搭配,用于捕捉执行过程中,用户是否按下ctrl+C终止程序while 1:n = n + 1animate_plot(n)time.sleep(0.005)
except KeyboardInterrupt:passfor i in range(0, len(servo_SIG)):servo[i].stop()  # 停止pwm
GPIO.cleanup()  # 清理GPIO引脚

实验总结

经过本知识点的学习和实验操作,你应该能达到以下水平:

知识点内容了解熟悉掌握
逆运动学运动学逆解的有无解、有无多解情况
逆运动学运动学逆解的求解
逆运动学几何法和代数法
硬件单腿舵机的简单校准
可视化动态可视化运动学计算结果

版权信息:教材尚未完善,此处预留版权信息处理方式
mini pupper相关内容可访问:https://github.com/mangdangroboticsclub

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

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

相关文章

Qt 事件机制

【1】事件 事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。 每种控件有自己可识别的事件&#xff0c;如窗体的加载、单击、双击等事件&#xff0c;编辑框&#xff08;文本框&#xff09;的文本改变事件等等。 事件就是用户对窗口上各种组件的操作。…

LibAFL的安装及基本使用

本教程安装LibAFL使用的是Ubuntu 22.04 操作系统 1. 安装 1.1 Rust 安装 Rust的安装&#xff0c;参照Rust官网&#xff1a;https://www.rust-lang.org/tools/install curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh1.2 LLVM安装 直接apt安装&#xff0c;安…

表格形式的Sarsa与Q_learning算法

环境如下&#xff1a; 这是一个简单的环境&#xff0c;绿色方块代表终点&#xff0c;白色方块代表可行点&#xff0c;灰色方块代表陷阱 用Sarsa算法和Q_learning算法训练得到value表格 代码如下&#xff1a; (jupyter notebook上的代码&#xff0c;所以顺序看起来有点儿奇怪) …

Java8 新特性强大的Stream API

一、Stream API 说明 Java8中有两大最为重要的改变。第一个是 Lambda 表达式&#xff1b;另外一个则是 Stream API。 Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充&#xff0c;因为Stream API可以极大提供Ja…

【博学谷学习记录】超强总结,用心分享丨人工智能 特征工程 特征变换 分箱学习总结

目录概念分箱的作用等频分箱等距分箱*卡方分箱公式例子概念 特征构造的过程中&#xff0c;对特征做分箱处理时必不可少的过程分箱就是将连续变量离散化&#xff0c;合并成较少的状态 分箱的作用 离散特征的增加和减少都很容易&#xff0c;易于模型的快速迭代&#xff1b;稀疏…

基于自定义训练函数的BP神经网络回归分析

目录 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的神经元 BP神经网络的激活函数 BP神经网络的传递函数 基于自定义训练函数的BP神经网络回归分析 背影 BP神经网络是一种成熟的神经网络&#xff0c;拥有很多训练函数&#xff0c;传递函数&#xff0c;激活函数&#x…

数学小课堂:无穷小(用动态和极限的眼光看世界)

文章目录 引言I 极限1.1 柯西对极限的认知1.2 极限准确的定义1.3 数列极限的定义1.4 函数极限的定义1.5 无穷小(特殊的极限)1.6 定量和逆向思维1.7 认知升级的过程引言 身处于渐变世界的人类,难以理解瞬间突变。 老师的作用,就是用大白话,把数学语言所写的知识,翻译成大…

腾讯安全与锐捷网络战略合作,威胁情报能力“被集成”

2月28日&#xff0c;腾讯安全和锐捷网络在北京联合举办“威胁情报”战略合作发布会。双方发布了一款集成了腾讯安全威胁情报的新一代防火墙&#xff0c;并举办战略合作签约仪式。会上&#xff0c;锐捷网络安全产品事业部总经理项小升、腾讯安全总经理陈龙代表双方签署战略合作协…

Python可视化界面编程入门

Python可视化界面编程入门具体实现代码如所示&#xff1a; &#xff08;1&#xff09;普通可视化界面编程代码入门&#xff1a; import sys from PyQt5.QtWidgets import QWidget,QApplication #导入两个类来进行程序界面编程if __name__"__main__":#创建一个Appl…

k8s学习之路 | Day17 k8s 工作负载

文章目录工作负载的定义工作负载资源分类工作负载的定义 官方参考链接&#xff1a;https://kubernetes.io/docs/concepts/workloads/ A workload is an application running on Kubernetes. Whether your workload is a single component or several that work together, on K…

《C++ Primer Plus》(第6版)第6章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第6章编程练习《C Primer Plus》&#xff08;第6版&#xff09;第6章编程练习1. 大小写转换2. 平均值3. 菜单4. 成员5. 收入所得税6. 捐款7. 统计单词8. 统计文件字符数9. 重写编程练习6《C Primer Plus》&#xff08;第6版&…

创建对象的方式和对属性的操作

javaScript支持多种编程范式&#xff0c;包括函数式编程和面向对象编程&#xff0c;javaScript的对象被设计成一组属性的无序集合&#xff0c;由key和value组成。 创建对象的两种方式 早期使用创建对象方式最多的是使用Object类&#xff0c;使用new关键字来创建一个对象&…

docker-compose安装kafka和php简单测试

docker-compose.yml内容&#xff1a; version: 3.1 services: zookeeper: container_name: zookeeper image: zookeeper:3.6 ports: - 2181:2181 kafka: image: wurstmeister/kafka container_name: kafka depends_on: - zookeeper …

解决Spring Data Jpa 实体类自动创建数据库表失败问题

先说一下我遇到的这个问题&#xff0c;首先我是通过maven创建了一个spring boot的工程&#xff0c;引入了Spring data jpa&#xff0c;结果实体类创建好之后&#xff0c;运行工程却没有在数据库中自动创建数据表。 找了半天发现是一个配置的问题! hibernate.ddl-auto节点的配…

【Python实战】激情澎湃,2023极品劲爆舞曲震撼全场,爬虫一键采集DJ大串烧,一曲醉人女声DJ舞曲,人人都听醉~(排行榜采集,妙啊~)

导语 哈喽&#xff01;大家好。我是木木子吖~今天给大家带来爬虫的内容哈。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 今天教大家Python爬虫实战一键采集大家喜欢的DJ舞曲哦&#xff01; …

01-Oracle入门基础知识讲解

本章内容主要是讲解Oracle基础知识&#xff0c;安装完Oracle后第一次使用所必须了解的一些常用软件及命令&#xff0c;Oracle的体系结构等知识。 一、进入SQL Plus客户端软件 1.进入SQLPLUS客户端windows界面 2.进入DOS窗口界面 普通用户登录&#xff1a;conn 用户名称/密码 …

taobao.user.avatar.get

&#xffe5;开放平台基础API不需用户授权 根据混淆nick查询用户头像 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 点击获取key和secret 请求参数 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey,…

实现RecyclerView二级列表

自定义RecyclerView的adapter实现二级列表 图片大于5MB&#xff0c;CSDN不让上传&#xff0c;使用github链接&#xff0c;如果看不到请使用科学上网 https://github.com/nanjolnoSat/PersonalProject/blob/recyclerexpandableadapter/Recyclerexpanableadapter/pic/pic1.gif 源…

Kotlin学习:5.2、异步数据流 Flow

Flow一、Flow1、Flow是什么东西&#xff1f;2、实现功能3、特点4、冷流和热流5、流的连续性6、流的构建器7、流的上下文8、指定流所在协程9、流的取消9.1、超时取消9.2、主动取消9.3、密集型任务的取消10、背压和优化10.1、buffer 操作符10.2、 flowOn10.3、conflate 操作符10.…

同为(TOWE)防雷产品助力福建移动南平分公司防雷改造

01 公司简介中国移动通信集团福建有限公司南平分公司属于福建移动地级分公司&#xff0c;所属行业为电信、广播电视和卫星传输服务。现已建成覆盖范围广、业务品种多、通信质量高的综合通信网络&#xff0c;具备行业领先的经营管理制度。移动通信大楼的综合防雷及地接系统&…