注明:
项目链接 https://github.com/howard789/puma560_trajectory_plan
机器人工具箱 https://petercorke.com/toolboxes/robotics-toolbox/

一,正逆解运算
二,新添代码
puma560_gui_sliders.m 正解
Matlab
function puma560_gui_sliders()
% PUMA560 6关节滑块GUI:拖动滑块实时更新机械臂,并显示末端位姿
% 依赖:项目已 addpath(genpath('demo'), genpath('my_trajectory_plan'))
% 复用:demo/show_forward.m (你已验证能输入角度并画出机械臂)
clc;
% 确保能找到 show_forward
if exist('show_forward','file') ~= 2
addpath(genpath('demo'), genpath('my_trajectory_plan'));
end
% 初始角度(度)
q0 = [30 25 -90 50 70 -80];
% UI
fig = uifigure('Name','PUMA560 Slider GUI', 'Position',[100 100 860 520]);
% 角度显示区
txt = uitextarea(fig, 'Position',[520 20 320 480], 'Editable','off', 'FontName','Consolas');
% 滑块区域
panel = uipanel(fig, 'Title','Joint Angles (deg)', 'Position',[20 20 480 480]);
labels = {'J1','J2','J3','J4','J5','J6'};
% 你可以按需要改范围(度)
% 建议先给个宽范围,后面再按你PUMA560实际限位收紧
mins = [-180 -180 -180 -180 -180 -180];
maxs = [ 180 180 180 180 180 180];
sl = gobjects(1,6);
ed = gobjects(1,6);
for i=1:6
y = 430 - (i-1)*70;
uilabel(panel, 'Text', labels{i}, 'Position',[10 y 30 22]);
sl(i) = uislider(panel, ...
'Limits',[mins(i) maxs(i)], ...
'Value', q0(i), ...
'Position',[50 y+10 320 3]);
ed(i) = uieditfield(panel, 'numeric', ...
'Value', q0(i), ...
'Position',[390 y 70 22], ...
'Limits',[mins(i) maxs(i)]);
end
% 一个"重置"按钮
uibutton(panel, 'Text','Reset', 'Position',[10 20 80 30], ...
'ButtonPushedFcn', @(~,~)resetAll());
% 一个"复制当前角度"按钮
uibutton(panel, 'Text','Copy q', 'Position',[110 20 80 30], ...
'ButtonPushedFcn', @(~,~)copyQ());
% 画图窗口(单独开一个 figure 更清爽)
robotFig = figure('Name','PUMA560 View', 'NumberTitle','off');
movegui(robotFig, 'northeast');
% 绑定回调:拖动滑块实时更新;在编辑框输入数字也更新
for i=1:6
sl(i).ValueChangingFcn = @(src,evt)onSliderChanging(i, evt.Value);
sl(i).ValueChangedFcn = @(src,evt)updateAll(); % 松手后再完整更新一次(更稳)
ed(i).ValueChangedFcn = @(src,evt)onEditChanged(i, src.Value);
end
% 首次显示
updateAll();
%% ---------- nested functions ----------
function q = getQ()
q = arrayfun(@(k) sl(k).Value, 1:6);
end
function setQ(q)
for k=1:6
sl(k).Value = q(k);
ed(k).Value = q(k);
end
end
function onSliderChanging(idx, val)
% 滑块拖动时:同步编辑框并快速刷新
ed(idx).Value = val;
quickUpdate();
end
function onEditChanged(idx, val)
% 编辑框改值:同步滑块并刷新
sl(idx).Value = val;
updateAll();
end
function resetAll()
setQ(q0);
updateAll();
end
function copyQ()
q = getQ();
s = sprintf('[%.2f %.2f %.2f %.2f %.2f %.2f]', q);
clipboard('copy', s);
uialert(fig, ['已复制到剪贴板: ' s], 'Copied');
end
function quickUpdate()
% 拖动时的"快速更新":为了不卡,尽量少做事
q = getQ();
updateRobotPlot(q);
updateText(q, []);
end
function updateAll()
q = getQ();
T = updateRobotPlot(q);
updateText(q, T);
end
function T = updateRobotPlot(q_deg)
% 关键:复用你项目的 show_forward
% 你的 show_forward 目前是"显示正解矩阵并显示 robot 状态"
% 如果 show_forward 能返回 T,就拿它;否则我们只显示文字"请改 show_forward 返回T"
figure(robotFig); %#ok<LFIG>
try
% 如果你已经把 show_forward 改成 function T=show_forward(joints)
T = show_forward(q_deg);
catch
% 若 show_forward 没返回值,也不影响"画图实时变化"
show_forward(q_deg);
T = [];
end
drawnow limitrate;
end
function updateText(q_deg, T)
% 更新右侧文本区域
lines = {};
lines{end+1} = sprintf('q(deg) = [%.2f %.2f %.2f %.2f %.2f %.2f]', q_deg);
lines{end+1} = '';
if isempty(T)
lines{end+1} = 'T: (show_forward 未返回 T,若要显示T请把 show_forward 改成 function T=show_forward(joints))';
else
p = T(1:3,4);
R = T(1:3,1:3);
lines{end+1} = 'T =';
lines{end+1} = mat2str(T, 4);
lines{end+1} = '';
lines{end+1} = sprintf('p = [%.4f, %.4f, %.4f]^T', p(1), p(2), p(3));
lines{end+1} = 'R =';
lines{end+1} = mat2str(R, 4);
end
txt.Value = lines;
end
end
puma560_gui_ik_pose.m 逆解
Matlab
function puma560_gui_ik()
% 末端位姿(滑块) -> inverse_kinematics(T) -> 8组解 -> 选择显示
% 依赖:
% - demo/show_forward.m (用来画机械臂)
% - inverse_kinematics.m (你贴的函数,输入4x4齐次矩阵,返回8组rad)
clc;
% 路径
if exist('show_forward','file') ~= 2 || exist('inverse_kinematics','file') ~= 2
addpath(genpath('demo'), genpath('my_trajectory_plan'));
end
if exist('inverse_kinematics','file') ~= 2
error('找不到 inverse_kinematics.m,请确认已 addpath 到工程目录。');
end
if exist('show_forward','file') ~= 2
error('找不到 show_forward.m,请确认 demo 目录已加入路径。');
end
% 初始末端位姿(建议你选一个"肯定可达"的)
pose0.xyz = [-0.2, -0.2, 0.2]; % x y z (单位按你工程:多半是米)
pose0.rpy = [0, 90, 0]; % roll pitch yaw (deg), ZYX
% UI
fig = uifigure('Name','PUMA560 IK GUI', 'Position',[100 100 1020 560]);
panel = uipanel(fig,'Title','End-Effector Pose (xyz + rpy)', 'Position',[20 20 640 520]);
txt = uitextarea(fig,'Position',[680 20 320 520], 'Editable','off', 'FontName','Consolas');
uilabel(panel,'Text','选择解:', 'Position',[10 30 60 22]);
dd = uidropdown(panel,'Items',{'1'},'Value','1','Position',[70 30 80 22]);
btnPlay = uibutton(panel,'Text','轮播', 'Position',[170 30 70 26]);
btnStop = uibutton(panel,'Text','停止', 'Position',[250 30 70 26], 'Enable','off');
btnCopyT = uibutton(panel,'Text','复制T', 'Position',[340 30 70 26]);
% 机器人显示窗口
robotFig = figure('Name','PUMA560 IK View','NumberTitle','off');
movegui(robotFig,'northeast');
% sliders
names = {'x','y','z','roll','pitch','yaw'};
xyzMin = [-0.6, -0.6, -0.2];
xyzMax = [ 0.6, 0.6, 0.8];
rpyMin = [-180, -180, -180];
rpyMax = [ 180, 180, 180];
mins = [xyzMin, rpyMin];
maxs = [xyzMax, rpyMax];
vals0 = [pose0.xyz, pose0.rpy];
sl = gobjects(1,6);
ed = gobjects(1,6);
for i=1:6
y = 450 - (i-1)*70;
uilabel(panel,'Text',names{i}, 'Position',[10 y 60 22]);
sl(i) = uislider(panel, ...
'Limits',[mins(i) maxs(i)], ...
'Value', vals0(i), ...
'Position',[70 y+10 420 3]);
ed(i) = uieditfield(panel,'numeric', ...
'Value', vals0(i), ...
'Limits',[mins(i) maxs(i)], ...
'Position',[510 y 110 22]);
sl(i).ValueChangingFcn = @(src,evt)onChanging(i, evt.Value);
sl(i).ValueChangedFcn = @(src,evt)updateAll();
ed(i).ValueChangedFcn = @(src,evt)onEdit(i, src.Value);
end
dd.ValueChangedFcn = @(~,~)updateRobotFromSelected();
btnPlay.ButtonPushedFcn = @(~,~)startPlay();
btnStop.ButtonPushedFcn = @(~,~)stopPlay();
btnCopyT.ButtonPushedFcn = @(~,~)copyT();
% timer for autoplay
timerObj = timer('ExecutionMode','fixedSpacing','Period',1.0,'TimerFcn',@(~,~)tick());
% 缓存当前解(避免每次点下拉都重新算)
Qdeg_cache = [];
valid_cache = false;
T_cache = eye(4);
updateAll();
%% -------- nested --------
function pose = getPose()
v = arrayfun(@(k) sl(k).Value, 1:6);
pose.xyz = v(1:3);
pose.rpy = v(4:6);
end
function onChanging(idx, val)
ed(idx).Value = val;
quickUpdate();
end
function onEdit(idx, val)
sl(idx).Value = val;
updateAll();
end
function T = poseToT(pose)
x = pose.xyz(1); y = pose.xyz(2); z = pose.xyz(3);
r = deg2rad(pose.rpy(1));
p = deg2rad(pose.rpy(2));
yaw = deg2rad(pose.rpy(3));
Rz = [cos(yaw) -sin(yaw) 0;
sin(yaw) cos(yaw) 0;
0 0 1];
Ry = [cos(p) 0 sin(p);
0 1 0;
-sin(p) 0 cos(p)];
Rx = [1 0 0;
0 cos(r) -sin(r);
0 sin(r) cos(r)];
R = Rz*Ry*Rx; % ZYX
T = [R [x;y;z]; 0 0 0 1];
end
function quickUpdate()
% 拖动时也更新,但少做点事(不弹窗/不花里胡哨)
updateAll(true);
end
function updateAll(isFast)
if nargin < 1, isFast = false; end
pose = getPose();
T = poseToT(pose);
% 调你工程的解析逆解:返回 cell 里装 8 组 joints(rad)
[valid, result_list] = inverse_kinematics(T);
T_cache = T;
valid_cache = logical(valid);
if ~valid_cache || isempty(result_list)
Qdeg_cache = [];
dd.Items = {'1'};
dd.Value = '1';
if ~isFast
txt.Value = buildTextNoSolution(T, pose);
end
return;
end
% cell -> 8x6 rad
Qrad = cell2mat(result_list(:)); % 8x6
Qdeg_cache = rad2deg(Qrad);
% 更新下拉
dd.Items = arrayfun(@num2str, 1:size(Qdeg_cache,1), 'UniformOutput', false);
if ~ismember(dd.Value, dd.Items)
dd.Value = dd.Items{1};
end
if ~isFast
txt.Value = buildText(T, pose, Qdeg_cache);
end
updateRobotFromSelected();
end
function updateRobotFromSelected()
if isempty(Qdeg_cache), return; end
idx = str2double(dd.Value);
idx = max(1, min(idx, size(Qdeg_cache,1)));
q_deg = Qdeg_cache(idx,:);
figure(robotFig); %#ok<LFIG>
show_forward(q_deg); % 你工程的 FK 可视化(输入"度")
title(sprintf('IK Solution %d: [%.1f %.1f %.1f %.1f %.1f %.1f] deg', ...
idx, q_deg(1),q_deg(2),q_deg(3),q_deg(4),q_deg(5),q_deg(6)));
drawnow limitrate;
end
function lines = buildTextNoSolution(T, pose)
lines = {};
lines{end+1} = 'No IK solution (valid=0).';
lines{end+1} = sprintf('xyz = [%.3f %.3f %.3f]', pose.xyz(1),pose.xyz(2),pose.xyz(3));
lines{end+1} = sprintf('rpy = [%.1f %.1f %.1f] deg (ZYX)', pose.rpy(1),pose.rpy(2),pose.rpy(3));
lines{end+1} = '';
lines{end+1} = 'T =';
lines{end+1} = mat2str(T, 4);
end
function lines = buildText(T, pose, Qdeg)
lines = {};
lines{end+1} = 'Target pose:';
lines{end+1} = sprintf('xyz = [%.3f %.3f %.3f]', pose.xyz(1),pose.xyz(2),pose.xyz(3));
lines{end+1} = sprintf('rpy = [%.1f %.1f %.1f] deg (ZYX)', pose.rpy(1),pose.rpy(2),pose.rpy(3));
lines{end+1} = '';
lines{end+1} = 'T =';
lines{end+1} = mat2str(T, 4);
lines{end+1} = '';
lines{end+1} = sprintf('Found %d solutions:', size(Qdeg,1));
for k=1:size(Qdeg,1)
q = Qdeg(k,:);
lines{end+1} = sprintf('%2d) [%.1f %.1f %.1f %.1f %.1f %.1f] deg', ...
k, q(1),q(2),q(3),q(4),q(5),q(6));
end
end
function startPlay()
btnPlay.Enable = 'off';
btnStop.Enable = 'on';
if strcmp(timerObj.Running,'off'), start(timerObj); end
end
function stopPlay()
btnPlay.Enable = 'on';
btnStop.Enable = 'off';
if strcmp(timerObj.Running,'on'), stop(timerObj); end
end
function tick()
if isempty(dd.Items), return; end
cur = str2double(dd.Value);
nxt = cur + 1;
if nxt > numel(dd.Items), nxt = 1; end
dd.Value = dd.Items{nxt};
updateRobotFromSelected();
end
function copyT()
clipboard('copy', mat2str(T_cache, 6));
uialert(fig, '已复制当前 T 到剪贴板(mat2str 格式)', 'Copied');
end
end
draw_shapes_with_puma560.m 路径规划
Matlab
function draw_shapes_with_puma560_onefig()
% 单窗口:机械臂动画 + 末端轨迹实时绘制(circle/rectangle/triangle/star)
% 依赖:inverse_kinematics(T) -> [valid, result_list] (8组rad)
% show_forward(q_deg) -> 绘制机械臂(输入度)
clear; clc; close all;
addpath(genpath('demo'), genpath('my_trajectory_plan'));
assert(exist('inverse_kinematics','file')==2, '找不到 inverse_kinematics.m');
assert(exist('show_forward','file')==2, '找不到 show_forward.m');
%% ====== 选择图形 ======
shape = "triangle"; % "circle" | "rectangle" | "triangle" | "star"
%% ====== 轨迹参数(你主要调这些)======
N = 30; % 点数(越大越顺)
dt = 0.02; % 帧间隔(秒)
center = [0.25, 0.00, 0.35]; % 图形中心
scale = 0.1; % 图形尺度
yaw_deg = 0;
pitch_deg = 90;
roll_deg = 0;
close_loop = true;
%% ====== 初始关节角(用于连续选解)======
q_prev_deg = [30 25 -90 50 70 -80];
q_prev_rad = deg2rad(q_prev_deg);
%% ====== 生成目标路径点 ======
P = generate_shape_points(shape, N, center, scale, close_loop);
T_list = cell(size(P,1),1);
for k = 1:size(P,1)
T_list{k} = make_T_from_xyz_rpy(P(k,:), [roll_deg pitch_deg yaw_deg]);
end
%% ====== 逆解并选连续解 ======
Q_deg = zeros(size(P,1), 6);
for k = 1:size(P,1)
Tk = T_list{k};
[valid, result_list] = inverse_kinematics(Tk);
if ~valid || isempty(result_list)
error('点 %d 无解:xyz=[%.3f %.3f %.3f],请调 center/scale/姿态', ...
k, P(k,1),P(k,2),P(k,3));
end
Qrad_all = cell2mat(result_list(:)); % 8x6
idx = select_closest_solution(Qrad_all, q_prev_rad);
q_sel_rad = Qrad_all(idx,:);
q_prev_rad = q_sel_rad;
Q_deg(k,:) = rad2deg(q_sel_rad);
end
%% ====== 单窗口动画:边动边画轨迹 ======
fig = figure('Name',"PUMA560 Draw: "+shape,'NumberTitle','off');
ax = axes('Parent',fig);
grid(ax,'on'); axis(ax,'equal');
xlabel(ax,'X'); ylabel(ax,'Y'); zlabel(ax,'Z');
view(ax, 3);
% 先画"目标轨迹虚线"(可选,不想要就注释)
hold(ax,'on');
plot3(ax, P(:,1), P(:,2), P(:,3), '--', 'LineWidth', 1);
% 实时轨迹线(从空开始)
hTrace = plot3(ax, nan, nan, nan, 'LineWidth', 2); % 不指定颜色,遵守你的偏好
% 用于缓存已经走过的点(这里用目标点作为轨迹;如果你想用"实际FK点",我也能改)
xt = nan(size(P,1),1);
yt = nan(size(P,1),1);
zt = nan(size(P,1),1);
% 动画循环
for k = 1:size(Q_deg,1)
% 1) 在同一个 figure 里画机器人
% show_forward 内部可能会 clear/重画当前figure,
% 为了让轨迹线不丢:我们每次 show_forward 后再把轨迹线画回去。
figure(fig);
show_forward(Q_deg(k,:)); % 你的函数:输入"度"
hold on;
% 2) 更新轨迹点
xt(k) = P(k,1);
yt(k) = P(k,2);
zt(k) = P(k,3);
set(hTrace, 'XData', xt(1:k), 'YData', yt(1:k), 'ZData', zt(1:k));
title(sprintf('Shape: %s | step %d/%d', shape, k, size(Q_deg,1)));
drawnow;
pause(dt);
end
disp('完成:单窗口边动边画轨迹。');
end
%% ====== 辅助函数 ======
function P = generate_shape_points(shape, N, center, scale, close_loop)
cx = center(1); cy = center(2); cz = center(3);
switch lower(shape)
case "circle"
t = linspace(0, 2*pi, N);
P = [cx + scale*cos(t(:)), cy + scale*sin(t(:)), cz + zeros(N,1)];
case "rectangle"
w = scale*2;
h = scale*1.2;
v = [ ...
cx-w/2, cy-h/2, cz;
cx+w/2, cy-h/2, cz;
cx+w/2, cy+h/2, cz;
cx-w/2, cy+h/2, cz];
P = polyline_resample(v, N, close_loop);
case "triangle"
R = scale*1.2;
ang = deg2rad([90, 210, 330]);
v = [cx+R*cos(ang(:)), cy+R*sin(ang(:)), cz+zeros(3,1)];
P = polyline_resample(v, N, close_loop);
case "star"
Rout = scale*1.4;
Rin = scale*0.55;
ang0 = deg2rad(90);
v = zeros(10,3);
for i=1:10
a = ang0 + (i-1)*pi/5;
r = Rout;
if mod(i,2)==0, r = Rin; end
v(i,:) = [cx + r*cos(a), cy + r*sin(a), cz];
end
P = polyline_resample(v, N, close_loop);
otherwise
error('未知 shape:%s', shape);
end
end
function P = polyline_resample(vertices, N, close_loop)
V = vertices;
if close_loop
V = [V; V(1,:)];
end
seg = diff(V,1,1);
L = sqrt(sum(seg.^2,2));
s = [0; cumsum(L)];
total = s(end);
su = linspace(0, total, N)';
P = zeros(N,3);
for dim=1:3
P(:,dim) = interp1(s, V(:,dim), su, 'linear');
end
end
function T = make_T_from_xyz_rpy(xyz, rpy_deg)
x = xyz(1); y = xyz(2); z = xyz(3);
roll = deg2rad(rpy_deg(1));
pitch= deg2rad(rpy_deg(2));
yaw = deg2rad(rpy_deg(3));
Rz = [cos(yaw) -sin(yaw) 0;
sin(yaw) cos(yaw) 0;
0 0 1];
Ry = [cos(pitch) 0 sin(pitch);
0 1 0;
-sin(pitch) 0 cos(pitch)];
Rx = [1 0 0;
0 cos(roll) -sin(roll);
0 sin(roll) cos(roll)];
R = Rz*Ry*Rx;
T = [R [x;y;z]; 0 0 0 1];
end
function idx = select_closest_solution(Qrad_all, q_prev_rad)
diff = Qrad_all - q_prev_rad;
d = sqrt(sum(diff.^2,2));
[~, idx] = min(d);
end