exponenta event banner

Аналитические решения обратной кинематики человекообразного робота

В этом примере показано, как вывести аналитические решения для обратной кинематики головной цепи человекообразного робота.

Шаг 1: Определение параметров

Опишите кинематику связи голова-цепь (связь между туловищем и головой) человекообразного робота NAO [1] с использованием параметров Денавита-Хартенберга (DH) и обозначений, основанных на исследовании Kofinas et al. [2]. Следующее преобразование определяет звено головной цепи.

T = ABase, 0T0,1T1,2RxRyA2, напор

где:

  • ABase,0 - перевод с основания (туловища) на соединительную или опорную рамку 0

  • T0,1 - ориентация привязки 1 относительно 0

  • T1,2 - ориентация привязки 2 относительно 1

  • Rx - вращение валка

  • Ry - поворот шага

  • A2,Head - перевод из ссылки 2 в конечную эффекторную точку (головку)

T (1: 3,4) определяет координаты головки, которые являются xc, yc, zc.

В этом примере аналитически решается задача обратной кинематики, возвращая все ориентации отдельных соединений в звене «голова-цепь» с заданными координатами головки xc, yc, и zc в пределах достижимого пространства. Затем для эффективности аналитические результаты преобразуются в чисто числовые функции.

Аналитическое решение (если это возможно) позволяет выполнять вычисления в реальном времени и избегать сингулярностей, что затрудняет численные методы.

Шаг 2: Определение прямой кинематики головной цепи с использованием параметров DH

Функция getKinematicChain возвращает конкретную кинематическую цепь для робота NAO в терминах символьных переменных. Для получения подробной информации о getKinematicChain, см. раздел Вспомогательные функции.

kinChain = 'head';
dhInfo = getKinematicChain(kinChain);
T = dhInfo.T
T = 

(r11r12r13xcr21r22r23ycr31r32r33zc0001)[r11, r12, r13, xc; r21, r22, r23, yc; r31, r32, r33, zc; sym(0), sym(0), sym(0), sym(1)]

Выражение матрицы преобразования прямой кинематики T в виде следующей последовательности продуктов: T = ABase0*T01*T12*Rx*Ry*A2Head. Определите отдельные матрицы следующим образом.

Укажите матрицу перемещения от туловища (основания) к соединению 0.

ABase0 = dhInfo.ABase0
ABase0 = 

(10000100001NeckOffsetZ0001)[sym(1), sym(0), sym(0), sym(0); sym(0), sym(1), sym(0), sym(0); sym(0), sym(0), sym(1), NeckOffsetZ; sym(0), sym(0), sym(0), sym(1)]

Укажите матрицу преобразования из соединения 0 в соединение 1.

T01 = dhInfo.T01
T01 = 

(cos(θ1)-sin(θ1)00sin(θ1)cos(θ1)0000100001)[cos(theta_1), -sin(theta_1), sym(0), sym(0); sin(theta_1), cos(theta_1), sym(0), sym(0); sym(0), sym(0), sym(1), sym(0); sym(0), sym(0), sym(0), sym(1)]

Задайте матрицу преобразования из соединения 1 в соединение 2.

T12 = dhInfo.T12
T12 = 

(cos(θ2-π2)-sin(θ2-π2)000010-sin(θ2-π2)-cos(θ2-π2)000001)[cos(theta_2 - sym(pi)/2), -sin(theta_2 - sym(pi)/2), sym(0), sym(0); sym(0), sym(0), sym(1), sym(0); -sin(theta_2 - sym(pi)/2), -cos(theta_2 - sym(pi)/2), sym(0), sym(0); sym(0), sym(0), sym(0), sym(1)]

Укажите матрицу вращения рулона.

Rx = dhInfo.Rx
Rx = 

(100000-1001000001)[sym(1), sym(0), sym(0), sym(0); sym(0), sym(0), -sym(1), sym(0); sym(0), sym(1), sym(0), sym(0); sym(0), sym(0), sym(0), sym(1)]

Укажите матрицу поворота шага.

Ry = dhInfo.Ry
Ry = 

(00100100-10000001)[sym(0), sym(0), sym(1), sym(0); sym(0), sym(1), sym(0), sym(0); -sym(1), sym(0), sym(0), sym(0); sym(0), sym(0), sym(0), sym(1)]

Укажите матрицу перемещения от соединения 2 к головке.

A2Head = dhInfo.A2Head
A2Head = 

(100CameraX0100001CameraZ0001)[sym(1), sym(0), sym(0), CameraX; sym(0), sym(1), sym(0), sym(0); sym(0), sym(0), sym(1), CameraZ; sym(0), sym(0), sym(0), sym(1)]

Известные параметры этой проблемы: CameraX, CameraZ, NeckOffsetZ, и позиции xc, yc, и zc. Неизвестными параметрами являются start1 и start2. После того, как вы найдёте, вы можете вычислить отдельные преобразования T. Затем робот может достичь желаемого положения. xc, yc, zc.

Хотя эти параметры можно увидеть в матрицах преобразования, они не существуют в качестве переменных в базовой рабочей области MATLAB. Это происходит потому, что эти параметры происходят от функции. Функции не используют базовую рабочую область. Каждая рабочая область функции отделена от основной рабочей области и всех других рабочих областей для защиты целостности данных. Таким образом, использовать эти переменные вне функции getKinematicChain, использовать syms для их создания.

syms CameraX CameraZ NeckOffsetZ xc yc zc theta_1 theta_2 real;

Шаг 3: Найти Алгебраические решения для

Переписать уравнение T = ABase0*T01*T12*Rx*Ry*A2Head, разделяя термины, которые описывают движения туловища и головы робота: inv(T01)*inv(ABase0)*T = T12*Rx*Ry*A2Head. Упростите левую и правую стороны уравнения и определите интересующие уравнения, соответствующие выражениям для координатных положений.

LHS = simplify(inv(T01)*inv(ABase0)*T);
RHS = simplify(T12*Rx*Ry*A2Head);
eqns = LHS(1:3,end) - RHS(1:3,end)
eqns = 

(xccos(θ1)-CameraZsin(θ2)-CameraXcos(θ2)+ycsin(θ1)yccos(θ1)-xcsin(θ1)zc-NeckOffsetZ-CameraZcos(θ2)+CameraXsin(θ2))[xc*cos(theta_1) - CameraZ*sin(theta_2) - CameraX*cos(theta_2) + yc*sin(theta_1); yc*cos(theta_1) - xc*sin(theta_1); zc - NeckOffsetZ - CameraZ*cos(theta_2) + CameraX*sin(theta_2)]

Эта система уравнений содержит две переменные, для которых вы хотите найти решения, Однако уравнения также подразумевают, что произвольный выбор невозможен xc, yc, и zc. Поэтому также рассмотрим yc в качестве переменной. Все остальные неизвестные системы являются символическими параметрами.

Этот пример следует типичному алгебраическому подходу для решения задач обратной кинематики [3]. Идея состоит в том, чтобы получить компактное представление решения, где выражение каждой переменной в терминах параметров и переменных, для которых вы уже решили уравнения. В качестве первого шага, найдите либо start1, либо start2 в терминах параметров. Затем выразите другую переменную в терминах известной переменной и параметров. Для этого сначала определите уравнение, имеющее только одну переменную.

intersect(symvar(eqns(1)),[theta_1,theta_2,yc])
ans = (θ1θ2yc)[theta_1, theta_2, yc]
intersect(symvar(eqns(2)),[theta_1,theta_2,yc])
ans = (θ1yc)[theta_1, yc]
intersect(symvar(eqns(3)),[theta_1,theta_2,yc])
ans = θ2theta_2

Третье уравнение содержит только одну переменную Сначала решите это уравнение.

[theta_2_sol,theta_2_params,theta_2_conds] = ...
    solve(eqns(3),theta_2,'Real',true,'ReturnConditions',true)
theta_2_sol = 

(2πk-2atan(CameraX-CameraX2+CameraZ2-NeckOffsetZ2+2NeckOffsetZzc-zc2CameraZ-NeckOffsetZ+zc)2πk-2atan(CameraX+CameraX2+CameraZ2-NeckOffsetZ2+2NeckOffsetZzc-zc2CameraZ-NeckOffsetZ+zc))[2*sym(pi)*k - 2*atan((CameraX - sqrt(CameraX^2 + CameraZ^2 - NeckOffsetZ^2 + 2*NeckOffsetZ*zc - zc^2))/(CameraZ - NeckOffsetZ + zc)); 2*sym(pi)*k - 2*atan((CameraX + sqrt(CameraX^2 + CameraZ^2 - NeckOffsetZ^2 + 2*NeckOffsetZ*zc - zc^2))/(CameraZ - NeckOffsetZ + zc))]

theta_2_params = kk
theta_2_conds = 

(kZCameraZ+zcNeckOffsetZNeckOffsetZ-zc2CameraX2+CameraZ2kZCameraZ+zcNeckOffsetZNeckOffsetZ-zc2CameraX2+CameraZ2)[in(k, 'integer')&CameraZ + zc ~= NeckOffsetZ&(NeckOffsetZ - zc)^2 <= CameraX^2 + CameraZ^2; in(k, 'integer')&CameraZ + zc ~= NeckOffsetZ&(NeckOffsetZ - zc)^2 <= CameraX^2 + CameraZ^2]

Растворы имеют аддитивный член 2xeonk с параметром k. Без потери обобщения можно установить k равным 0 в решениях и условиях.

theta_2_sol = subs(theta_2_sol,theta_2_params,0)
theta_2_sol = 

(-2atan(CameraX-CameraX2+CameraZ2-NeckOffsetZ2+2NeckOffsetZzc-zc2CameraZ-NeckOffsetZ+zc)-2atan(CameraX+CameraX2+CameraZ2-NeckOffsetZ2+2NeckOffsetZzc-zc2CameraZ-NeckOffsetZ+zc))[-2*atan((CameraX - sqrt(CameraX^2 + CameraZ^2 - NeckOffsetZ^2 + 2*NeckOffsetZ*zc - zc^2))/(CameraZ - NeckOffsetZ + zc)); -2*atan((CameraX + sqrt(CameraX^2 + CameraZ^2 - NeckOffsetZ^2 + 2*NeckOffsetZ*zc - zc^2))/(CameraZ - NeckOffsetZ + zc))]

for p = 1:numel(theta_2_conds)
    theta_2_conds(p) = simplify(subs(theta_2_conds(p),theta_2_params,0));
end

Теперь решите для переменных start1 и yc в терминах переменной start2 и других символических параметров.

[theta_1_sol,yc_sol,yc_theta_1_params,yc_theta_1_conds] = ...
    solve(eqns(1:2),theta_1,yc,'Real',true,'ReturnConditions',true);

Выведите на экран решения для yc.

theta_1_sol
theta_1_sol = 

(2πk-σ1σ2+2πk2πk-σ22πk-π2σ1+2πk2atan(x)+2πkπ2+2πk2πk-π2π2+2πk)where  σ1=2atan(CameraX+xcCameraX-xcCameraX-xc)  σ2=2atan(σ3σ5+1+σ5σ3σ5+1σ4)  σ3=-xc-CameraX+CameraXσ5+xcσ5-2CameraZtan(θ22)σ4  σ4=CameraX+xc-CameraXσ5+xcσ5+2CameraZtan(θ22)  σ5=tan(θ22)2[2*sym(pi)*k - 2*atan(sqrt((CameraX + xc)*(CameraX - xc))/(CameraX - xc)); 2*atan((sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2)))/(tan(theta_2/2)^2 + 1) + (tan(theta_2/2)^2*sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2))))/(tan(theta_2/2)^2 + 1))/(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2))) + 2*sym(pi)*k; 2*sym(pi)*k - 2*atan((sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2)))/(tan(theta_2/2)^2 + 1) + (tan(theta_2/2)^2*sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2))))/(tan(theta_2/2)^2 + 1))/(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2))); 2*sym(pi)*k - sym(pi)/2; 2*atan(sqrt((CameraX + xc)*(CameraX - xc))/(CameraX - xc)) + 2*sym(pi)*k; 2*atan(x) + 2*sym(pi)*k; sym(pi)/2 + 2*sym(pi)*k; 2*sym(pi)*k - sym(pi)/2; sym(pi)/2 + 2*sym(pi)*k]

yc_sol
yc_sol = 

(CameraX+xcCameraX-xcσ1-σ1CameraX-CameraX+xcCameraX-xc0σ2-σ2-CameraX)where  σ1=-xc-CameraX+σ3+xctan(θ22)2-σ4CameraX+xc-σ3+xctan(θ22)2+σ4tan(θ22)2+1  σ2=-σ3+σ4+CameraXtan(θ22)2+1  σ3=CameraXtan(θ22)2  σ4=2CameraZtan(θ22)[sqrt((CameraX + xc)*(CameraX - xc)); sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2)))/(tan(theta_2/2)^2 + 1); -sqrt(-(xc - CameraX + CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 - 2*CameraZ*tan(theta_2/2))*(CameraX + xc - CameraX*tan(theta_2/2)^2 + xc*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2)))/(tan(theta_2/2)^2 + 1); CameraX; -sqrt((CameraX + xc)*(CameraX - xc)); sym(0); (- CameraX*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2) + CameraX)/(tan(theta_2/2)^2 + 1); -(- CameraX*tan(theta_2/2)^2 + 2*CameraZ*tan(theta_2/2) + CameraX)/(tan(theta_2/2)^2 + 1); -CameraX]

Решения зависят от двух параметров, k и x.

yc_theta_1_params
yc_theta_1_params = (kx)[k, x]

Растворы имеют аддитивный термин 2xeonk. Без потери обобщения можно установить k равным 0 в решении для theta_1_sol и условия yc_theta_1_conds.

theta_1_sol = simplify(subs(theta_1_sol,yc_theta_1_params,[0,0]))
theta_1_sol = 

(-2atan(CameraX+xcCameraX-xcCameraX-xc)σ1-σ1-π22atan(CameraX+xcCameraX-xcCameraX-xc)0π2-π2π2)where  σ1=2atan(CameraXcos(θ2)-xc+CameraZsin(θ2)xc+CameraXcos(θ2)+CameraZsin(θ2)xc+CameraXcos(θ2)+CameraZsin(θ2))[-2*atan(sqrt((CameraX + xc)*(CameraX - xc))/(CameraX - xc)); 2*atan(sqrt((CameraX*cos(theta_2) - xc + CameraZ*sin(theta_2))*(xc + CameraX*cos(theta_2) + CameraZ*sin(theta_2)))/(xc + CameraX*cos(theta_2) + CameraZ*sin(theta_2))); -2*atan(sqrt((CameraX*cos(theta_2) - xc + CameraZ*sin(theta_2))*(xc + CameraX*cos(theta_2) + CameraZ*sin(theta_2)))/(xc + CameraX*cos(theta_2) + CameraZ*sin(theta_2))); -sym(pi)/2; 2*atan(sqrt((CameraX + xc)*(CameraX - xc))/(CameraX - xc)); sym(0); sym(pi)/2; -sym(pi)/2; sym(pi)/2]

for p = 1:numel(yc_theta_1_conds)
    yc_theta_1_conds(p) = simplify(subs(yc_theta_1_conds(p),yc_theta_1_params,[0,0]));
end

Аналогичная замена не требуется для yc_sol поскольку нет зависимости от параметров.

intersect(symvar(yc_sol),yc_theta_1_params)
 
ans =
 
Empty sym: 1-by-0
 

Шаг 4: Проверка решений

Начав с произвольного набора известных числовых значений, вычислите числовые значения конечных положений эффектора для xc, yc, и zc с прямой кинематикой. Затем, работать назад от xc, yc, и zc чтобы вычислить все возможные числовые значения для start1 и start2 с использованием выражений обратной кинематики из предыдущего вычисления. Убедитесь в том, что один набор обратных решений соответствует начальному набору числовых значений для start1 и start2.

Установите фиксированные параметры для робота. Значения в этом примере приведены только в целях иллюстрации.

CameraXValue = 53.9; 
CameraZValue = 67.9; 
NeckOffsetZValue = -5;

Используя прямые вычисления, найдите конечные положения эффектора, соответствующие значениям start1 и start2.

Tfk = ABase0*T01*T12*Rx*Ry*A2Head;
xyz = Tfk(1:3,end);

Создайте символическую функцию для этих позиций.

xyzFunc = symfun(subs(xyz,[CameraX,CameraZ,NeckOffsetZ], ...
  [CameraXValue,CameraZValue,NeckOffsetZValue]),[theta_1,theta_2]);

Для прямой кинематики укажите две переменные theta_1_known и theta_2_known , которые содержат произвольный начальный набор значений для start1 и start2.

theta_1_known = [pi/4,pi/6,pi/8,pi/10];
theta_2_known = [pi/8,-pi/12,pi/16,-pi/10];
numPts = numel(theta_1_known);
num_theta_2 = numel(theta_2_sol);
num_theta_1 = numel(theta_1_sol);

Обратите внимание, что потенциально существуют num_theta_1 решения для каждого num_theta_2 решение.

Используйте следующую последовательность операций для проверки решений.

  1. Закольцовывание (theta_1_known,theta_2_known). Для каждой точки вычислите конечные положения x_known, y_known, и z_known, которые затем «известны.»

  2. Петля по решениям для соответствия θ2 x_known и z_known. Для каждого решения проверьте наличие соответствующего условия. cond_theta_2 является действительным. Если условие действительно, вычислите соответствующее числовое решение theta_2_derived.

  3. Закольцовывание решений для («1»), соответствующих («1») x_known, z_known, и theta_2_derived. Для каждого решения проверьте наличие соответствующего условия. cond_theta_1 является действительным. Если условие действительно, проверьте, y_derived численно соответствует y_known в пределах относительных и абсолютных допусков через условие cond_yc. Если это условие действительно, вычислите theta_1_derived.

  4. Сбор результатов в таблице T для отображения.

T = table([],[],[],[],'VariableNames',{'theta_1_known','theta_2_known',...
    'theta_1_derived','theta_2_derived'});

for ix1 = 1:numPts
xyz_known = double(xyzFunc(theta_1_known(ix1),theta_2_known(ix1)));
x_known = xyz_known(1);
y_known = xyz_known(2);
z_known = xyz_known(3);
for ix2 = 1:num_theta_2 % theta_2 loop
  cond_theta_2 = subs(theta_2_conds(ix2),[CameraX,CameraZ,NeckOffsetZ,xc,zc],...
                 [CameraXValue,CameraZValue,NeckOffsetZValue,x_known,z_known]);
  if isAlways(cond_theta_2) % if theta_2 is valid
    theta_2_derived = subs(theta_2_sol(ix2),[CameraX,CameraZ,NeckOffsetZ,xc,zc],...
                      [CameraXValue,CameraZValue,NeckOffsetZValue,x_known,z_known]);
    theta_2_derived = double(theta_2_derived);
    for ix3 = 1:num_theta_1 % theta_1 loop
      cond_theta_1 = subs(yc_theta_1_conds(ix3),[CameraX,CameraZ,NeckOffsetZ,theta_2,xc,zc],...
              [CameraXValue,CameraZValue,NeckOffsetZValue,theta_2_derived,x_known,z_known]);
      if isAlways(cond_theta_1) % if theta_1 is valid
        y_derived = subs(yc_sol(ix3),[CameraX,CameraZ,NeckOffsetZ,theta_2,xc,zc],...
                [CameraXValue,CameraZValue,NeckOffsetZValue,theta_2_derived,x_known,z_known]);
        y_derived = double(y_derived);
        cond_yc = abs(y_known - y_derived) < max(100*eps,1e-6*abs(y_known)); % check rounding
        if isAlways(cond_yc) % if yc is valid
          theta_1_derived = subs(theta_1_sol(ix3),[CameraX,CameraZ,NeckOffsetZ,theta_2,xc,zc],...
                  [CameraXValue,CameraZValue,NeckOffsetZValue,theta_2_derived,x_known,z_known]);
          theta_1_derived = double(theta_1_derived);
          T = vertcat(T,table(theta_1_known(ix1),theta_2_known(ix1),theta_1_derived,...
              theta_2_derived,'VariableNames',T.Properties.VariableNames));
        end
      end
    end
  end
end
end
T
T=8×4 table
    theta_1_known    theta_2_known    theta_1_derived    theta_2_derived
    _____________    _____________    _______________    _______________

        0.7854           0.3927            0.7854             0.3927    
        0.7854           0.3927           -2.3562            -1.7346    
        0.5236          -0.2618            0.5236            -0.2618    
        0.5236          -0.2618            -2.618            -1.0801    
        0.3927          0.19635            0.3927            0.19635    
        0.3927          0.19635           -2.7489            -1.5383    
       0.31416         -0.31416           0.31416           -0.31416    
       0.31416         -0.31416           -2.8274            -1.0278    

Обратите внимание, что существует две возможные пары решений (theta_1_derived,theta_2_derived) полученные с использованием обратной кинематики для каждой пары стартовых углов (theta_1_known,theta_2_known). Один набор обратных решений соответствует начальным углам.

Ссылки

  1. SoftBank Robotics. НАО. https://www.softbankrobotics.com/emea/en/nao.

  2. Кофинас, Н., Э. Орфанудакис, и М. Г. Лагудакис. «Полная аналитическая обратная кинематика для NAO». В 2013 году 13-я Международная конференция по автономным системам роботов. Лиссабон, Португалия: Роботика, 2013.

  3. Кендрикс, К. «Решение задачи обратной кинематической робототехники: сравнительное исследование матрицы Денавита - Хартенберга и теории базиса Гробнера». Доктор философии. Дипломная работа. Обернский университет, Оберн, AL, 2007.

Вспомогательные функции

function kinChain = getKinematicChain(link)
% This function returns the kinematic chain of the NAO humanoid robot
% represented using Denavit-Hartenberg (DH) parameters.
% The function uses as a reference the paper: Complete analytical inverse
% kinematics for NAO by Kofinas, N., Orfanoudakis, E., Lagoudakis, M.G.,
% 2013 13th International Conference on Autonomous Robot Systems
% (Robotica), Publication Year: 2013.
    if nargin < 1
        link = 'head';
    end
    % Notation: A, R, and T are the translation, rotation, and DH
    % transformation matrices, respectively.
    % Specify DH parameters for the desired end configuration.
    syms r11 r12 r13 r21 r22 r23 r31 r32 r33 xc yc zc real;
    R = [r11 r12 r13;r21 r22 r23;r31 r32 r33];
    kinChain.T = [R,[xc;yc;zc];0,0,0,1];
    switch link
        case 'head' % Kinematic chain from torso (base) to head.
            syms CameraX CameraZ NeckOffsetZ real;
            % Translation from torso (base) to joint 0.
            ABase0 = getA([0 0 NeckOffsetZ]);
            % Translation from joint 2 to head.
            A2Head = getA([CameraX 0 CameraZ]);
            % Transformation from joint 0 to joint 1.
            % theta_1 is the rotation angle corresponding to the 0-1 link.
            syms theta_1 real;
            alpha1 = 0; a1 = 0; d1 = 0;
            T01 = getT(a1,alpha1,d1,theta_1);
            % Transformation from joint 1 to joint 2.
            % theta_2 is the rotation angle corresponding to the 1-2 link.
            syms theta_2 real;
            piby2 = str2sym('pi/2');
            d2 = 0; a2 = 0; alpha2 = -piby2;
            T12 = getT(a2,alpha2,d2,theta_2-piby2);
            % Rx is the roll rotation.
            Rx = getR('x',piby2);
            % Ry is the pitch rotation.
            Ry = getR('y',piby2);
            % Capture the kinematic chain as a string.
            % The transformation is from the base to the head.
            kinChain.DHChain = 'ABase0*T01*T12*Rx*Ry*A2Head';
            kinChain.ABase0 = ABase0;
            kinChain.T01 = T01;
            kinChain.T12 = T12;
            kinChain.Rx = Rx;
            kinChain.Ry = Ry;
            kinChain.A2Head = A2Head;
        case 'lefthand' % Kinematic chain from torso to left hand.
            syms ShoulderOffsetY ElbowOffsetY ShoulderOffsetZ real;
            ABase0 = getA([0 (ShoulderOffsetY+ElbowOffsetY) ShoulderOffsetZ]);
            syms HandOffsetX LowerArmLength real;
            AEnd4 = getA([(HandOffsetX+LowerArmLength) 0 0]);
            piby2 = str2sym('pi/2');
            syms theta_1 real;
            alpha1 = -piby2; a1 = 0; d1 = 0;
            T01 = getT(a1,alpha1,d1,theta_1);
            syms theta_2 real;
            d2 = 0; a2 = 0; alpha2 = piby2;
            T12 = getT(a2,alpha2,d2,theta_2-piby2);
            syms UpperArmLength theta_3 real;
            d3 = UpperArmLength; a3 = 0; alpha3 = -piby2;
            T32 = getT(a3,alpha3,d3,theta_3);
            syms theta_4 real;
            d4 = 0; a4 = 0; alpha4 = piby2;
            T43 = getT(a4,alpha4,d4,theta_4);
            Rz = getR('z',piby2);
            kinChain.DHChain = 'ABase0*T01*T12*T32*T43*Rz*AEnd4';
            kinChain.ABase0 = ABase0;
            kinChain.T01 = T01;
            kinChain.T12 = T12;
            kinChain.T32 = T32;
            kinChain.T43 = T43;
            kinChain.Rz = Rz;
            kinChain.AEnd4 = AEnd4;
    end
end

function A = getA(vec)
% This function returns the translation matrix.
    A = [1 0 0 vec(1);
        0 1 0 vec(2);
        0 0 1 vec(3);
        0 0 0 1];
end

function R = getR(orientation,theta)
% This function returns the rotation matrix.
    switch orientation
        case 'x'
            R = [1 0 0 0;
                0 cos(theta) -sin(theta) 0;
                0 sin(theta) cos(theta) 0
                0 0 0 1];
        case 'y'
            R = [cos(theta) 0 sin(theta) 0;
                0 1 0 0;
                -sin(theta) 0 cos(theta) 0;
                0 0 0 1];
        case 'z'
            R = [cos(theta) -sin(theta) 0 0;
                sin(theta) cos(theta) 0 0;
                0 0 1 0;
                0 0 0 1];
    end
end

function T = getT(a,alpha,d,theta)
% This function returns the Denavit-Hartenberg (DH) matrix.
    T = [cos(theta),-sin(theta),0,a;
         sin(theta)*cos(alpha),cos(theta)*cos(alpha),-sin(alpha),-sin(alpha)*d;
         sin(theta)*sin(alpha),cos(theta)*sin(alpha),cos(alpha),cos(alpha)*d;
         0,0,0,1];
end