Consulting Services
حساساتمقالات

تفهيم حساس IMU: نُظم تمثيل اتجاهية الجسم وربط قيم الحساس مع مكتبة VPython ثلاثية الأبعاد

طالعنا في المقالة السابقة مبدأ عمل حساسي التسارع والجايروسكوب داخل حساس الـIMU ، وتعرفنا على ميزات كل منهما، واستخلصنا قوانين حساب الزوايا الثلاث الإمالة pitch والدحرجة roll والانعراج yaw ، وثم استعرضنا كيفية تحسين ممانعة الإشارات المستخلصة من الضجيج، وفي مقالة أخرى، تحدثنا عن الحساس المغناطيسي وكيفية معايرته للحصول على زاوية البوصلة azimuth. في هذه المقالة، نطالع الأطر frames of reference المستخدمة في حساس الـIMU وتحديداً الإطار العطالي inertial frame وإطار الجسم body frame وهما الإطاران الأكثر شهرةً، ومن ثم نناقش محدودية استخدام الزوايا الثلاث لتمثيل النظام عند الزاوية 90 درجة وهذا ما يسمى بظاهرة Gimbal-lock، ومن ثم نتكلم عن معالجة ذلك باستخدام نظم تمثيل أخرى وهي axis-angle و نظام الكواتيرنيون Quaternion، وختاماً نقوم بتمثيل الدارة في الفضاء ثلاثي الأبعاد باستخدام مكتبة Vpython.

مشروع جهاز تحديد القبلة مع تعويض الإمالة. مشروع يستخدم المفاهيم الموجودة في أجزاء السلسلة الثلاث.

الأُطر المستخدمة في التعامل مع حساس الـIMU

إن قيم إحداثيات أي نقطة تتبع لإطار مرجعي frame of reference (محاور إحداثيات) معين ويكون الإطار المرجعي نسبي. إن أحد أهم الإطارات المستخدمة في حساس الـ IMU هو الإطار العطالي inertial frame وهو ثابت على سطح الأرض وإطار الجسم body frame وهو إطار متطابق مع جسم الحساس. نحتاج لإطارات وسيطة عند الحاجة للتحويل من الإطار العطالي إلى إطار الجسم وهي الإطارين vehicle-1 و vehicle-2. يوجد أُطر أخرى مستخدمة ولكن نكتفي بذكرالإطارين الأساسيين العطالي وإطار الجسم.

إن الإطار العطالي ثابت غير متحرك يكون فيه المحور x بجهة الشمال والمحور y بجهة الشرق والمحور z بجهة الجاذبية

صورة توضح الإطار العطالي حيث  المحور x بجهة الشمال والمحور y بجهة الشرق والمحور z بجهة الجاذبيةمصدر الصورة: الورقة المرجعية AN-1005 من CH Robotics

لنفترض أن الطائرة انزاحت عن الشمال بزاوية انعراج Yaw، إن هذا الانعراج سيؤثر على المحورين x و y كما في الشكل أدناه

الإطار vehicle-1 الناتج عن إضافة زاوية انعراج Yaw إلى الإطار العطالي، وهذا يؤثر على المحورين x و y . مصدر الصورة: الورقة المرجعية AN-1005 من CH Robotics

لتحويل شعاع من الإطار العطالي إلى الإطار vehicle-1 نستخدم مصفوفة التحويل. إن مصفوفة التحويل rotation matrix هي المصفوفة الحاصل جدائها بقيم الشعاع في الإطار العطالي تساوي فيم ذات الشعاع ولكن بالنسبة للإطار الجديد. 

يمكن استنتاج مصفوقة التحويل باستخدام قوانين المثلثات وتم ذكرها في الصورة التوضيحية أدناه:

اشتقاق مصفوفة التحويل باستخدام نقطة P كمثال. بفرض إدارة المحاور بزاوية إنعراج فإن مصفوفة التحويل بين النقطتين قبل وبعد الإدارة مستنتجة كما في الشكل باستخدام قوانين المثلثات . 

يمكن تعميم المصفوفة الموضحة في الشكل السابق على فضاء ثلاثي الأبعاد إذ أن زاوية الانعراج لا تسبب تغير في المحور z وبهذا تكون المصفوفة 

R_I^{v 1}(\psi)=\left[\begin{array}{ccc}\cos (\psi) & \sin (\psi) & 0 \\ -\sin (\psi) & \cos (\psi) & 0 \\ 0 & 0 & 1 \end{array}\right]

إن الإطار vehicle-2 ينتج عن زاوية الإمالة عن الإطار vehicle-1 وإن مصفوفة التحويل تتميز هذه المرة بأنها ثابتة عند المحور y لأن الإمالة تكون بالدوران على المحور y. 

R_{v 1}^{v 2}(\theta)=\left[\begin{array}{ccc} \cos (\theta) & 0 & -\sin (\theta) \\ 0 & 1 & 0 \\ \sin (\theta) & 0 & \cos (\theta) \end{array}\right]

الإطار vehicle-2 الناتج عن إضافة زاوية الإمالة Pitch إلى الإطارvehicle-1، وهذا يؤثر على المحورين x و z . مصدر الصورة: الورقة المرجعية AN-1005 من CH Robotics

للوصول لإطار الجسم Body frame نضيف زاوية الدحرجة التي تكون حول المحور X ولذلك إن مصفوفة التحويل هذه المرّة ثابتة على محور X. 

R_{v 2}^B(\phi)=\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos (\phi) & \sin (\phi) \\ 0 & -\sin (\phi) & \cos (\phi) \end{array}\right]

يمكن التعبير عن مصفوفة التحويل من الإطار العطالي إلى إطار الجسم بالطريقة التالية: 

R_I^B(\phi, \theta, \psi)=R_{v 2}^B(\phi) R_{v 1}^{v 2}(\theta) R_I^{v 1}(\psi)

هل تمثيل معلومات التوجيه بالزوايا الثلاث كافي ؟

إن صيغة أويلر  Euler angles  هي تعبير عن حالة توجيه الجسم . يفشل هذا التمثيل عندما تقترب زاوية الإمالة من 90 درجة وتعرف هذه الظاهرة بـ Gimbal Lock ويمكن إثباتها رياضياً ولكن لن نتبحّر في تفصيلها هذه المرة ويكفي معرفة أن هذه الظاهرة تعني خسارة واحدة من درجات الحريّة الثلاث للنظام عند الزاوية 90 درجة. يمكن الاطلاع على المراجع لشرح وافي عن هذه الظاهرة.  بسبب هذه المحدودية يُستخدم التمثيل بالكواتيرنيون Quaternion التي سنشرح لاحقاً والذي لا يعاني من ظاهرة Gimbal Lock .

يجب التنبيه أن حركة الجسم في تمثيل أويلر يتم عبر سلسلة من الحركات عبر المحاور الثلاثة (الإمالة – الدحرجة – الانعراج)، وهذا طبعاً من الناحية الرياضية وليس كحركة فيزيائية إذا فعلياً نحرك الجسم بحرية مطلقة بالهواء، ولكن النموذج الرياضي الذي اشتققنا منه معادلات المقالة السابقة معتمد على وجود سلسلة من الحركات المتتابعة والمرتّبة. 

مثال على الحركة اللازم القيام بها لنقل الجسم من الحالة الابتدائية (رقم واحد) إلى الحالة الهدف (رقم أربعة). في الأتراس المتداخلة نفترض الترس الأساسي هو الأخضر وحركته تسبب حركة الترسين في المستوى الثاني والثالث والترس الثاني هو الأزرق وحركته تسبب حركة ترس المستوى الثالث فقط والترس الأخير هو الأحمر وحركته لا تؤثر على المستويين الأعليين. الصور المستخدمة مأخوذة من فيديو على يوتيوب بعنوان  Euler (gimbal lock) Explained من قناة GuerrillaCG. على الرغم أننا في الواقع لا نتبع هذه الخطوات عندما نريد تحريك الجسم ولكن بعض النماذج الرياضية ومنها صيغة أويلر تفترض وجود هذه التروس المتداخلة ولهذا تحدث ظاهرة الـ Gimbal Lock. 

للتخلص من ظاهرة الـ Gimbal Lock نعتمد على نموذج تتم فيه الحركة دفعة واحدة بافتراض شعاع وزاوية دوران حول هذا الشعاع. 

تمثيل حركة الجسم بشعاع v وزاوية أو ما يسمى بتمثيل Axis-angle . الصورة مأخوذة من سلاديات المحاضرة العاشرة من كورس ee267 من جامعة ستانفورد

إن  Axis-angle representation هو تمثيل بديل عن تمثيل زوايا التوجيه الثلاث، ويعتمد هذا التمثيل البديل عن شعاع يمثل توجه الجسم في الفضاء الثلاثي بالإضافة إلى زاوية تمثيل زاوية الجسم حول هذا الشعاع. نستطيع التحويل من تمثيل axis-angle إلى الكواتيرنيون كما سنشرح في الفقرة التالية.

التمثيل بالكواتيرنيون Quaternion

إن الكواتيرنيون هو تمثيل بديل عن تمثيل أويلر المتمثلة بالزوايا الثلاث: الإمالة والدحرجة والانعراج. يمكن التبديل بين نظامي التمثيل باستخدام صيغ رياضية محددة سنذكرها. إن الكواتيرنيون هو شعاع مكون من أربعة أرقام، 3 منها أرقام تخيلية ورقم حقيقي يعبر عن طويلة الشعاع.

q=q_w+i q_x+j q_y+k q_z

للتحويل من axis-angle إلى الكواتيرنيون نستخدم الصيغة التالية، مع ملاحظة أن مركبات شعاع الدوران موضّحة في الشكل السابق: 

q(\theta, v)=\underbrace{\cos \left(\frac{\theta}{2}\right)}_{q_w}+i \underbrace{v_x \sin \left(\frac{\theta}{2}\right)}_{q_x}+\underbrace{j v_y \sin \left(\frac{\theta}{2}\right)}_{q_y}+\underbrace{k v_z \sin \left(\frac{\theta}{2}\right)}_{q_z}

لتحويل شعاع من الإطار العطالي إلى إطار الجسم فإننا نستخدم المعالدلة التالية: 

\mathbf{v}_B=\mathbf{q}_i^b\left(\begin{array}{c}0 \\ \mathbf{v}_I\end{array}\right)\left(\mathbf{q}_i^b\right)^{-1}

نلاحظ أننا أضفنا عنصر صفر لشعاع الدوران في الإطار العطالي داخل معادلة التحويل، ويجب معرفة أن عملية الضرب في  الكواتيرنيون من أجل كواتيرنيون أول \mathbf{q}_1=\left[\begin{array}{llll}a_1 & b_1 & c_1 & d_1\end{array}\right]^T وثاني \mathbf{q}_2=\left[\begin{array}{llll}a_2 & b_2 & c_2 & d_1\end{array}\right]^T هي: 

\mathbf{q}_1 \mathbf{q}_2=\left(\begin{array}{c}\mathrm{a}_1 \mathrm{a}_2-\mathrm{b}_1 \mathrm{~b}_2-\mathrm{c}_1 \mathrm{c}_2-\mathrm{d}_1 \mathrm{~d}_2 \\ \mathrm{a}_1 \mathrm{~b}_2+\mathrm{b}_1 \mathrm{a}_2+\mathrm{c}_1 \mathrm{~d}_2-\mathrm{d}_1 c_2 \\ \mathrm{a}_1 \mathrm{c}_2-\mathrm{b}_1 \mathrm{~d}_2+\mathrm{c}_1 \mathrm{a}_2+d_1 \mathrm{~b}_2 \\ \mathrm{a}_1 \mathrm{~d}_2+\mathrm{b}_1 \mathrm{c}_2-\mathrm{c}_1 \mathrm{~b}_2+\mathrm{d}_1 \mathrm{a}_2\end{array}\right)

إن مقلوب الكواتيرنيون conjugate هو ناتج قلب إشارة عناصره التخيلية فقط

conj(a + b i + c j + d k) = a - b i - c j - d k

 نلاحظ أننا نحتاج أن نقوم بعملتي ضرب، ولكن يمكن استخدام وبناء مصفوفة تحويل مباشرة وهي: 

R_i^b\left(\mathbf{q}_i^b\right)=\left[\begin{array}{ccc}a^2+b^2-c^2-d^2 & 2 b c-2 a d & 2 b d+2 a c \\ 2 b c+2 a d & a^2-b^2+c^2-d^2 & 2 c d-2 a b \\ 2 b d-2 a c & 2 c d+2 a b & a^2-b^2-c^2+d^2\end{array}\right]

ويكون التحويل بالشكل: 

\mathbf{v}_B=R_i^b\left(\mathbf{q}_i^b\right) \mathbf{v}_I

للحصول على زوايا التوجيه الثلاث من الكواتيرنيون نستخدم المعادلات الثالث التالية: 

\begin{aligned} & \phi=\arctan \left(\frac{2(a b+c d)}{a^2-b^2-c^2+d^2}\right) \\ & \theta=-\arcsin (2(b d-a c)) \\ & \psi=\arctan \left(\frac{2(a d+b c)}{a^2+b^2-c^2-d^2}\right)\end{aligned}

تمثيل الدارة عبر الـ VPython 

سنستخدم مكتبة Vpython لعرض سطح الدارة وتمثيلها في الفضاء ثلاثي الأبعاد، ولتمثيل السطح الذي يتموضع عليه الحساس IMU فإننا نعبر عنه بشعاع يصل بين نقطة الصفر في الفضاء الثلاثي (نقطة التقاء المحاور) ونقطة ذات إحداثيات x,y,z. لحساب هذه النقاط الثلاثة نبدأ من الزوايا الثلاثة المحسوبة عبر الحساس وهي زوايا الإمالة والدحرجة والانعراج. إن زاوية الانعراج تتشكل بين المحور x ومسقط شعاع الممثل للجسم على السطح XY وإن مطال هذا المسقط هو h. وباستخدام قوانين المثلثات نجد أن: 

x= h.cos(\psi)

y= h.sin(\psi)

سنفترض أن مطال شعاع السطح h هو واحدي.

h = 1. cos(\theta)

ويبقى الإحداثية الثالثة z 

z = 1. sin(\theta)

وبالمجمل 

\begin{bmatrix} x\\ y\\ z\\ \end{bmatrix} = \begin{bmatrix} cos(\theta).cos(\psi)\\ cos(\theta).sin(\psi)\\ sin(\theta)\\ \end{bmatrix}

توضيح لكيفية حساب نقطة الشعاع الممثل للجسم  

ملاحظة: إن أسهم الإحداثيات في مكتبة VPython موضحة في الأسفل، ونلاحظ أن محور y يحل محل محور z في التمثيل الذي اعتمدناه، ولهذا تبقى حسابات الشكل السابق صحيحة ولكن فقط نعكس بين قيم z و y .

شكل يوضّح الإطار المستخدم في مكتبة VPython. ويوضح الشكل أن طول الجسم يكون على المحور x و ارتفاعه على المحور y وعرضه على المحور z. كما يوجد شعاعين رئيسين للجسم وهو شعاع المحور axis وشعاع up. 

إن الكود المرجعي المستخدم لربط قيم الحساس مع توجيه الشكل ثلاثي الأبعاد معتمد على برنامج مكتوب في سلسلة دروس paul mcwhorterعن حساس الـ IMU وتحديداً الدرس الثامن عشر. تم إضافة تعديل وهو تعيين صورة لوجه الجسم الثلاثي الأبعاد. إن الكود الأصلي يعتمد على رسم أشكال ثلاثية الأبعاد تقريبية تعبيراً عن الدارة، ولكن وجدت أن الأكثر تعبيراً هو إضافة صورة لها على سطح المجسم ثلاثي الأبعاد. ولسبب ما فإن مكتبة Vpython لا تسمع بتعيين صورة للجسم إلا على الجوانب، ولحل هذه المشكلة عينت الصورة على الجانب الأيمن وأدرت الجسم بزاوية 90 درجة وهذا ما يفسر السطرين التاليين:

bBoard=box(texture={'file':'board.jpg','place':['right'] },length=.2,width=2,height=6,opacity=.8)
bBoard.rotate(angle=np.pi/2,axis=vector(0,0,1))

يرجى مراجعة الشكل السابق لمعرفة ترتيب المحاور والأبعاد في بيئة Vpython. سنعين ثلاث محاور ثابتة في الشكل تعبر عن محاور البيئة بالألوان (أحمر:x أخصر:y أزرق:z)

xarrow=arrow(lenght=2, shaftwidth=.1, color=color.red,axis=vector(1,0,0))
yarrow=arrow(lenght=2, shaftwidth=.1, color=color.green,axis=vector(0,1,0))
zarrow=arrow(lenght=4, shaftwidth=.1, color=color.blue,axis=vector(0,0,1))
 

وسنضيف محاور ثلاثة أخرى منطبقة على حسم الدارة وسنقوم بتحريكها لاحقاً في البرنامج 

frontArrow=arrow(length=4,shaftwidth=.1,color=color.purple,axis=vector(1,0,0))
upArrow=arrow(length=1,shaftwidth=.1,color=color.magenta,axis=vector(0,1,0))
sideArrow=arrow(length=2,shaftwidth=.1,color=color.orange,axis=vector(0,0,1))

نستخلص من الزوايا الثلاث المستقبلة عبر الاتصال التسلسلي الشعاع المعبر عن محور الجسم 

	body_x,body_y,body_z = cos(yaw)*cos(pitch),sin(pitch),sin(yaw)*cos(pitch)

نسمي هذا الشعاع بـ axis vector كما تسميه المكتبة 

	k=vector(body_x,body_y,body_z)

وللتعبير عن الجسم بمحاور ثلاث متعامدة منطبقة على الجسم وتتحرك حسب حركة الجسم فلابد من إيجاد المحورين المتعامدين مع الشعاع K أو axis vector وذلك يتم رياضياً عبر الجداء الشعاعي cross product  للشعاع k مع شعاع وسيط مؤقت وهو شعاع على محور y  

	k=vector(body_x,body_y,body_z)
	y=vector(0,1,0)
	s=cross(k,y)
	v=cross(s,k)

إن اشتقاقنا لمعادلات حساب مركبات الشعاع x,y,z لن تتغير إذا تغيرت زاوية الدحرجة، يمكن ملاحظة أن المركبات تم حسابها باستخدام زاوية الميلان والانعراج، ولهذا نستخدم صيغة دوران رودريغوس Rodrigues rotation formula والتي نستخدمها لحساب مركبات الشعاع الجديدة بعد الدحرجة.

إن صيغة رودريغوس تنص على وجود شعاع v في الفضاء الحقيقي ثلاثي الأبعاد وهذا الشعاع يدور حول شعاع واحدي k بزاوية theta بحسب قاعد اليد اليمنى. لإسقاط ذلك على مسألتنا، فإن زاوية الدوران في الصيغة هي زاوية الدحرجة وإن الشعاع v هو الشعاع المعبر عن up vectpr في مكتبة Vpython والشعاع k هو الشعاع axis vector. مع ملاحظة أن الشعاعين v و k متعامدين وبالتالي جداؤهم الداخلي inner product يساوي الصفر وبالتالي نحذف المركبة الأخيرة من صيغة ردوريغوس 

\mathbf{v}_{\text {rot }}=\mathbf{v} \cos \theta+(\mathbf{k} \times \mathbf{v}) \sin \theta+\mathbf{k}(\mathbf{k} \cdot \mathbf{v})(1-\cos \theta)

ومع حذف المركبة الأخيرة يتبقى من الصيغة: 

\mathbf{v}_{\text {rot }}=\mathbf{v} \cos \theta+(\mathbf{k} \times \mathbf{v}) \sin \theta

وهذا ما نجده في برنامج البايثون

vrot=v*cos(roll)+cross(k,v)*sin(roll)

ونعين أشعة الدارة في Vpython بهذا الشكل 

	bBoard.axis=vrot
	bBoard.up=k

نعين أيضاً الأشعة المنطبقة على جسم الدارة والتي استخلصناها عبر أجداء الأشعة سابقاً 

	frontArrow.axis=k
	sideArrow.axis=cross(k,vrot)
	upArrow.axis=v

يجب تعديل رقم المنفذ في الكود قبل تشغيله

from vpython import *
from time import *
from visual import *
import numpy as np
import math
import serial


ad=serial.Serial('/dev/ttyACM0',115200)
sleep(1)

canvas(title='IMU Data 3D Visualization', caption='A caption')

scene.range=5
toRad=2*np.pi/360
toDeg=1/toRad
scene.forward=vector(-1,-1,-1)

scene.width=800
scene.height=800

xarrow=arrow(lenght=2, shaftwidth=.1, color=color.red,axis=vector(1,0,0))
yarrow=arrow(lenght=2, shaftwidth=.1, color=color.green,axis=vector(0,1,0))
zarrow=arrow(lenght=4, shaftwidth=.1, color=color.blue,axis=vector(0,0,1))

frontArrow=arrow(length=4,shaftwidth=.1,color=color.purple,axis=vector(1,0,0))
upArrow=arrow(length=1,shaftwidth=.1,color=color.magenta,axis=vector(0,1,0))
sideArrow=arrow(length=2,shaftwidth=.1,color=color.orange,axis=vector(0,0,1))

bBoard=box(texture={'file':'board.jpg','place':['right'] },length=.2,width=2,height=6,opacity=.8)
bBoard.rotate(angle=np.pi/2,axis=vector(0,0,1))

while (True):
    while (ad.inWaiting()==0):
        pass
    roll,pitch,yaw= .0,.0,.0     
    try:
            dataPacket=ad.readline()
            dataPacket=str(dataPacket,'utf-8')
            splitPacket=dataPacket.split(",")
            roll=float(splitPacket[1])*toRad
            pitch=float(splitPacket[0])*toRad
            yaw=float(splitPacket[2])*toRad+np.pi
            yaw = -yaw
    except:
        pass
    log = 'Roll={},Pitch={},Yaw={}'.format(roll*toDeg, pitch*toDeg,yaw*toDeg)
    print(log)
    scene.caption = log
     
    rate(50)

    body_x,body_y,body_z = cos(yaw)*cos(pitch),sin(pitch),sin(yaw)*cos(pitch)
    k=vector(body_x,body_y,body_z)

    y=vector(0,1,0)
    s=cross(k,y)
    v=cross(s,k)
    vrot=v*cos(roll)+cross(k,v)*sin(roll)

    frontArrow.axis=k
    sideArrow.axis=cross(k,vrot)
    upArrow.axis=v
    bBoard.axis=vrot
    bBoard.up=k
    sideArrow.length=2
    frontArrow.length=4
    upArrow.length=1
    bBoard.width = 2
    bBoard.height = 6
    bBoard.length = .2

مراجع

Yahya Tawil

مؤسس عتاديات وأسعى لدعم المحتوى العربي عن النظم المضمنة في الويب العربي. خبرتي في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والمخططات وإنشاء المحتوى.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.

زر الذهاب إلى الأعلى