من الألف إلى الياء: كيف صممت لوحة مفاتيح أحلام المطور
نشرت: 2022-03-11أثناء العمل في أحد أيام أغسطس من عام 2007 ، لم أستطع إلا أن أدرك أن لوحة مفاتيح الكمبيوتر العادية لم تخدمني بقدر الإمكان. اضطررت إلى تحريك يدي بين الكتل المختلفة للوحة المفاتيح بشكل مفرط ، مئات إن لم يكن آلاف المرات في اليوم ، وكانت يدي قريبة بشكل غير مريح من بعضهما البعض. اعتقدت أنه لا بد من وجود طريقة أفضل.
تبع هذا الإدراك شعور غامر بالإثارة عندما فكرت في تخصيص أفضل لوحة مفاتيح للمطورين - ولاحقًا ، أدركت أنني ، بصفتي مطور برامج مدمج مستقل ، كنت جاهلًا بشكل يائس بشأن الأجهزة.
في ذلك الوقت ، كنت مشغولًا جدًا بمشاريع أخرى ، لكن لم يمر يوم لم أفكر فيه في إنشاء لوحة مفاتيح للقرصنة. وسرعان ما بدأت في تخصيص وقت فراغي للعمل في المشروع. تمكنت من تعلم مجموعة مهارات جديدة بالكامل ، وإقناع صديق لي ، أندراس فولجي ، مهندس ميكانيكي استثنائي ، للانضمام إلى المشروع ، وجمع بعض الأشخاص الرئيسيين ، وتخصيص وقت كافٍ لإنشاء نماذج أولية للعمل. في الوقت الحاضر ، أصبحت لوحة مفاتيح Ultimate Hacking حقيقة واقعة. نحن نحقق تقدمًا يوميًا ، وإطلاق حملتنا للتمويل الجماعي في متناول اليد.
يعد الانتقال من خلفية برمجية ، وعدم معرفة أي شيء عن الإلكترونيات ، إلى تصميم وبناء جهاز قوي وقابل للتسويق ، تجربة ممتعة ورائعة. في هذه المقالة ، سأصف تصميم كيفية عمل هذه التحفة الإلكترونية. قد يساعدك الفهم الأساسي لمخططات الدوائر الإلكترونية على المتابعة.
كيف تصنع لوحة مفاتيح؟
بعد أن كرست آلاف الساعات من حياتي لهذا الموضوع ، أصبح من الصعب علي أن أعطي إجابة مختصرة ، ولكن هناك طريقة شيقة للإجابة على هذا السؤال. ماذا لو بدأنا بشيء بسيط ، مثل لوحة Arduino ، وقمنا ببنائها تدريجياً لتكون لوحة مفاتيح Ultimate Hacking؟ لا ينبغي أن يكون أكثر قابلية للفهم فحسب ، بل يجب أن يكون تعليميًا للغاية. لذلك ، دع رحلة البرنامج التعليمي للوحة المفاتيح تبدأ!
الخطوة الأولى: لوحة مفاتيح بدون مفاتيح
أولاً ، دعنا نصنع لوحة مفاتيح USB تصدر الحرف x
على أساس مرة واحدة في الثانية. تعد لوحة تطوير Arduino Micro مرشحًا مثاليًا لهذا الغرض ، لأنها تتميز بالمتحكم الدقيق ATmega32U4 - متحكم AVR الدقيق ونفس المعالج الذي يمثل أدمغة UHK.
عندما يتعلق الأمر بوحدات التحكم الدقيقة AVR التي تدعم USB ، فإن إطار عمل USB خفيف الوزن لـ AVRs (LUFA) هو المكتبة المفضلة. إنها تمكن هذه المعالجات من أن تصبح عقول الطابعات أو أجهزة MIDI أو لوحات المفاتيح أو أي نوع آخر من أجهزة USB تقريبًا.
عند توصيل جهاز بمنفذ USB ، يتعين على الجهاز نقل بعض هياكل البيانات الخاصة التي تسمى واصفات USB. تخبر هذه الواصفات الكمبيوتر المضيف بنوع وخصائص الجهاز المتصل ، ويتم تمثيلها ببنية شجرة. لجعل الأمور أكثر تعقيدًا ، لا يمكن للجهاز تنفيذ وظيفة واحدة فحسب ، بل وظائف متعددة. دعونا نرى هيكل واصفات UHK:
- واصف الجهاز
- واصف التكوين
- واصف الواجهة 0: GenericHID
- واصف نقطة النهاية
- واصف الواجهة 1: لوحة المفاتيح
- واصف نقطة النهاية
- واصف الواجهة 2: الماوس
- واصف نقطة النهاية
- واصف الواجهة 0: GenericHID
- واصف التكوين
تعرض معظم لوحات المفاتيح القياسية واصف واجهة لوحة مفاتيح واحدة فقط ، وهو أمر منطقي. ومع ذلك ، بصفتها لوحة مفاتيح برمجة مخصصة ، تعرض UHK أيضًا واصف واجهة الماوس ، لأن المستخدم يمكنه برمجة مفاتيح عشوائية للوحة المفاتيح للتحكم في مؤشر الماوس بحيث يمكن استخدام لوحة المفاتيح كفأرة. تعمل واجهة GenericHID كقناة اتصال لتبادل معلومات التكوين لجميع الميزات الخاصة للوحة المفاتيح. يمكنك مشاهدة التنفيذ الكامل للجهاز وأوصاف التكوين الخاصة بـ UHK في LUFA هنا.
الآن بعد أن أنشأنا الواصفات ، حان الوقت لإرسال الحرف x
في كل ثانية.
uint8_t isSecondElapsed = 0; int main(void) { while (1) { _delay_us(1000); isSecondElapsed = 1; } } bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, uint8_t* const ReportID, const uint8_t ReportType, void* ReportData, uint16_t* const ReportSize) { USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData; if (isSecondElapsed) { KeyboardReport->KeyCode[0] = HID_KEYBOARD_SC_X; isSecondElapsed = 0; } *ReportSize = sizeof(USB_KeyboardReport_Data_t); return false; }
USB هو بروتوكول تم الاستقصاء عنه ، مما يعني أن الكمبيوتر المضيف يستعلم عن الجهاز على فترات منتظمة (عادة 125 مرة في الثانية) لمعرفة ما إذا كانت هناك أي بيانات جديدة لإرسالها. رد الاتصال ذو الصلة هو وظيفة CALLBACK_HID_Device_CreateHIDReport()
، والتي في هذه الحالة ترسل scancode للحرف x
إلى المضيف كلما احتوى المتغير isSecondElapsed
على 1
. يتم تعيين isSecondElapsed
على 1
من الحلقة الرئيسية على أساس كل ثانية ، ويتم تعيينه على 0
من رد الاتصال.
الخطوة الثانية: لوحة مفاتيح من أربعة مفاتيح
في هذه المرحلة ، لوحة المفاتيح الخاصة بنا ليست مفيدة بشكل رهيب. سيكون من الرائع أن نكتب عليها بالفعل. لكن من أجل ذلك نحتاج إلى مفاتيح ، ويجب ترتيب المفاتيح في مصفوفة لوحة مفاتيح. يمكن أن تحتوي لوحة المفاتيح كاملة الحجم والمكونة من 104 مفاتيح على 18 صفًا و 6 أعمدة ، لكن سيكون لدينا ببساطة مصفوفة لوحة مفاتيح متواضعة 2 × 2 لبدء التشغيل. هذا هو التخطيطي:
وهذه هي الطريقة التي تبدو عليها على لوح التجارب:
بافتراض أن ROW1
متصل بـ PINA0
و ROW2
إلى PINA1
و COL1
إلى PORTB0
و COL2
إلى PORTB1
، فإليك ما يبدو عليه رمز المسح:
/* A single pin of the microcontroller to which a row or column is connected. */ typedef struct { volatile uint8_t *Direction; volatile uint8_t *Name; uint8_t Number; } Pin_t; /* This part of the key matrix is stored in the Flash to save SRAM space. */ typedef struct { const uint8_t ColNum; const uint8_t RowNum; const Pin_t *ColPorts; const Pin_t *RowPins; } KeyMatrixInfo_t; /* This Part of the key matrix is stored in the SRAM. */ typedef struct { const __flash KeyMatrixInfo_t *Info; uint8_t *Matrix; } KeyMatrix_t; const __flash KeyMatrixInfo_t KeyMatrix = { .ColNum = 2, .RowNum = 2, .RowPins = (Pin_t[]) { { .Direction=&DDRA, .Name=&PINA, .Number=PINA0 }, { .Direction=&DDRA, .Name=&PINA, .Number=PINA1 } }, .ColPorts = (Pin_t[]) { { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB0 }, { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB1 }, } }; void KeyMatrix_Scan(KeyMatrix_t *KeyMatrix) { for (uint8_t Col=0; Col<KeyMatrix->Info->ColNum; Col++) { const Pin_t *ColPort = KeyMatrix->Info->ColPorts + Col; for (uint8_t Row=0; Row<KeyMatrix->Info->RowNum; Row++) { const Pin_t *RowPin = KeyMatrix->Info->RowPins + Row; uint8_t IsKeyPressed = *RowPin->Name & 1<<RowPin->Number; KeyMatrix_SetElement(KeyMatrix, Row, Col, IsKeyPressed); } } }
يقوم الكود بمسح عمود واحد في كل مرة ويقرأ داخل هذا العمود حالات مفاتيح التبديل الفردية. ثم يتم حفظ حالة المفاتيح الرئيسية في مصفوفة. ضمن وظيفة CALLBACK_HID_Device_CreateHIDReport()
السابقة الخاصة بنا ، سيتم إرسال رموز المسح الضوئي ذات الصلة بناءً على حالة تلك المصفوفة.
الخطوة الثالثة: لوحة مفاتيح بنصفين
حتى الآن ، أنشأنا بدايات لوحة مفاتيح عادية. ولكن في هذا البرنامج التعليمي الخاص بلوحة المفاتيح ، نهدف إلى توفير بيئة عمل متقدمة ، ونظرًا لأن الأشخاص لديهم يدين ، فمن الأفضل إضافة نصف لوحة مفاتيح أخرى إلى هذا المزيج.
سيحتوي النصف الآخر على مصفوفة لوحة مفاتيح أخرى ، تعمل بنفس طريقة العمل السابقة. الشيء الجديد المثير هو الاتصال بين نصفي لوحة المفاتيح. البروتوكولات الثلاثة الأكثر شيوعًا لربط الأجهزة الإلكترونية هي SPI و I 2 C و UART. لأغراض عملية سوف نستخدم UART في هذه الحالة.
يتدفق الاتصال ثنائي الاتجاه عبر RX يمينًا ومن خلال TX إلى اليسار وفقًا للرسم البياني أعلاه. VCC و GND ضروريان لنقل الطاقة. تحتاج UART إلى أقرانهم لاستخدام نفس معدل البث بالباود ، وعدد بتات البيانات وعدد بتات التوقف. بمجرد إعداد جهاز الإرسال والاستقبال UART لكلا الزملاء ، يمكن أن يبدأ الاتصال في التدفق.
في الوقت الحالي ، يرسل نصف لوحة المفاتيح اليسرى رسائل أحادية البايت إلى النصف الأيمن من لوحة المفاتيح عبر UART ، والتي تمثل الضغط على المفاتيح أو أحداث تحرير المفاتيح. يعالج النصف الأيمن من لوحة المفاتيح هذه الرسائل ويعالج حالة مصفوفة مصفوفة لوحة المفاتيح الكاملة في الذاكرة وفقًا لذلك. هذه هي الطريقة التي يرسل بها النصف الأيسر من لوحة المفاتيح الرسائل:
USART_SendByte(IsKeyPressed<<7 | Row*COLS_NUM + Col);
يبدو رمز النصف الأيمن من لوحة المفاتيح لتلقي الرسالة كما يلي:

void KeyboardRxCallback(void) { uint8_t Event = USART_ReceiveByte(); if (!MessageBuffer_IsFull(&KeyStateBuffer)) { MessageBuffer_Insert(&KeyStateBuffer, Event); } }
يتم تشغيل معالج المقاطعة KeyboardRxCallback()
عندما يتم تلقي بايت عبر UART. بالنظر إلى أن معالجات المقاطعة يجب أن تنفذ بأسرع ما يمكن ، يتم وضع الرسالة المستلمة في المخزن المؤقت الحلقي للمعالجة اللاحقة. تتم معالجة المخزن المؤقت الحلقي في النهاية من داخل الحلقة الرئيسية وسيتم تحديث مصفوفة لوحة المفاتيح بناءً على الرسالة.
ما ورد أعلاه هو أبسط طريقة لتحقيق ذلك ، لكن البروتوكول النهائي سيكون أكثر تعقيدًا إلى حد ما. سيتعين التعامل مع الرسائل متعددة البايت ، وسيتعين فحص الرسائل الفردية للتأكد من سلامتها باستخدام المجاميع الاختبارية CRC-CCITT.
في هذه المرحلة ، يبدو نموذجنا الأولي للوح التجارب مثيرًا للإعجاب:
الخطوة الرابعة: تعرف على شاشة LED
كان أحد أهدافنا مع UHK هو تمكين المستخدم من تحديد خرائط لوحة مفاتيح متعددة خاصة بالتطبيقات لزيادة الإنتاجية. يحتاج المستخدم إلى طريقة ما ليكون على دراية بخريطة المفاتيح الفعلية المستخدمة ، لذلك تم دمج شاشة LED المدمجة في لوحة المفاتيح. فيما يلي عرض نموذج أولي مع إضاءة جميع مصابيح LED:
يتم تنفيذ شاشة LED بواسطة مصفوفة 8x6 LED:
يمثل كل صفين من رموز LED ذات اللون الأحمر شرائح أحد شاشات LED المكونة من 14 مقطعًا. تمثل رموز LED البيضاء مؤشرات الحالة الثلاثة الإضافية.
لدفع التيار من خلال LED وإضاءته ، يتم ضبط العمود المقابل على الجهد العالي والصف المقابل على الجهد المنخفض. من النتائج المثيرة للاهتمام لهذا النظام أنه ، في أي لحظة معينة ، يمكن تمكين عمود واحد فقط (كل المصابيح الموجودة في هذا العمود والتي يجب أن تضاء تم ضبط الصفوف المقابلة لها على الجهد المنخفض) ، بينما يتم تعطيل بقية الأعمدة . قد يعتقد المرء أن هذا النظام لا يمكنه العمل على استخدام مجموعة كاملة من مصابيح LED ، ولكن في الواقع يتم تحديث الأعمدة والصفوف بسرعة كبيرة بحيث لا يمكن رؤية أي وميض بالعين البشرية.
يتم تشغيل مصفوفة LED بواسطة دائرتين مدمجتين (ICs) ، أحدهما يقود صفوفه والآخر يقود أعمدته. IC المصدر الذي يقود الأعمدة هو برنامج تشغيل PCA9634 I2C LED:
إن حوض مصفوفة LED IC الذي يقود الصفوف هو سجل تحويل الطاقة TPIC6C595:
دعنا نرى الكود ذي الصلة:
uint8_t LedStates[LED_MATRIX_ROWS_NUM]; void LedMatrix_UpdateNextRow(bool IsKeyboardColEnabled) { TPIC6C595_Transmit(LedStates[ActiveLedMatrixRow]); PCA9634_Transmit(1 << ActiveLedMatrixRow); if (++ActiveLedMatrixRow == LED_MATRIX_ROWS_NUM) { ActiveLedMatrixRow = 0; } }
يتم LedMatrix_UpdateNextRow()
كل مللي ثانية تقريبًا ، مع تحديث صف من مصفوفة LED. تخزن مصفوفة LedStates
حالة مصابيح LED الفردية ، ويتم تحديثها عبر UART استنادًا إلى الرسائل التي تم إنشاؤها من النصف الأيمن للوحة المفاتيح ، إلى حد كبير بالطريقة نفسها كما في حالة حدث الضغط على المفتاح / تحرير المفتاح.
الصورة الكبيرة
حتى الآن قمنا ببناء جميع المكونات الضرورية للوحة المفاتيح المخصصة للقراصنة بشكل تدريجي ، وحان الوقت لرؤية الصورة الكبيرة. يشبه الجزء الداخلي من لوحة المفاتيح شبكة كمبيوتر صغيرة: الكثير من العقد مترابطة. الفرق هو أن المسافة بين العقد لا تقاس بالأمتار أو الكيلومترات ، ولكن بالسنتيمترات ، والعقد ليست أجهزة كمبيوتر كاملة ، ولكنها دوائر صغيرة متكاملة.
لقد قيل الكثير حتى الآن حول تفاصيل جانب الجهاز للوحة مفاتيح المطور ، ولكن ليس كثيرًا عن UHK Agent ، برنامج جانب المضيف. والسبب هو أنه ، على عكس الأجهزة والبرامج الثابتة ، فإن Agent بدائي للغاية في هذه المرحلة. ومع ذلك ، يتم تحديد البنية عالية المستوى للوكيل ، والتي أود مشاركتها.
UHK Agent هو تطبيق التهيئة الذي يمكن من خلاله تخصيص لوحة المفاتيح لتناسب احتياجات المستخدم. على الرغم من كونه عميلاً ثريًا ، إلا أن الوكيل يستخدم تقنيات الويب ويعمل على قمة منصة node-webkit.
يتواصل الوكيل مع لوحة المفاتيح باستخدام مكتبة node-usb عن طريق إرسال طلبات تحكم USB خاصة خاصة بالجهاز ومعالجة نتائجها. يستخدم Express.js لعرض واجهة برمجة تطبيقات REST لاستهلاكها بواسطة تطبيقات الطرف الثالث. كما أنه يستخدم Angular.js لتوفير واجهة مستخدم أنيقة.
var enumerationModes = { 'keyboard' : 0, 'bootloader-right' : 1, 'bootloader-left' : 2 }; function sendReenumerateCommand(enumerationMode, callback) { var AGENT_COMMAND_REENUMERATE = 0; sendAgentCommand(AGENT_COMMAND_REENUMERATE, enumerationMode, callback); } function sendAgentCommand(command, arg, callback) { setReport(new Buffer([command, arg]), callback); } function setReport(message, callback) { device.controlTransfer( 0x21, // bmRequestType (constant for this control request) 0x09, // bmRequest (constant for this control request) 0, // wValue (MSB is report type, LSB is report number) interfaceNumber, // wIndex (interface number) message, // message to be sent callback ); }
يحتوي كل أمر على معرف 8 بت ومجموعة من الوسائط الخاصة بالأمر. حاليًا ، يتم تنفيذ أمر إعادة التعداد فقط. يقوم sendReenumerateCommand()
بإعادة تعداد الجهاز باعتباره أداة تحميل التشغيل اليسرى أو أداة تحميل التشغيل اليمنى ، أو لترقية البرنامج الثابت ، أو كجهاز لوحة مفاتيح.
قد لا يكون لدى المرء أي فكرة عن الميزات المتقدمة التي يمكن أن يحققها هذا البرنامج ، لذلك سأذكر القليل منها: سيكون الوكيل قادرًا على تصور تآكل المفاتيح الفردية وإخطار المستخدم بمتوسط العمر المتوقع ، حتى يتمكن المستخدم شراء اثنين من المفاتيح الرئيسية الجديدة للإصلاح الوشيك. سيوفر الوكيل أيضًا واجهة مستخدم لتكوين خرائط المفاتيح المختلفة وطبقات لوحة مفاتيح المتسللين. يمكن أيضًا ضبط سرعة مؤشر الماوس وتسارعه ، جنبًا إلى جنب مع الكثير من ميزات uber الأخرى. السماء هي الحد.
إنشاء النموذج
يتم بذل الكثير من العمل لإنشاء نماذج أولية مخصصة للوحة المفاتيح. بادئ ذي بدء ، يجب الانتهاء من التصميم الميكانيكي ، وهو معقد جدًا بحد ذاته ويتضمن أجزاء بلاستيكية مصممة خصيصًا ، وألواح من الفولاذ المقاوم للصدأ مقطوعة بالليزر ، وموجهات فولاذية مطحونة بدقة ومغناطيس نيوديميوم يربط نصفي لوحة المفاتيح معًا. تم تصميم كل شيء في CAD قبل بدء التصنيع.
هكذا تبدو حالة لوحة المفاتيح المطبوعة ثلاثية الأبعاد:
بناءً على التصميم الميكانيكي والتخطيطي ، يجب تصميم لوحة الدوائر المطبوعة. يبدو PCB الأيمن مثل هذا في KiCad:
ثم يتم تصنيع PCB ويجب لحام المكونات المثبتة على السطح يدويًا:
أخيرًا ، بعد تصنيع جميع الأجزاء ، بما في ذلك الطباعة ثلاثية الأبعاد والتلميع وطلاء الأجزاء البلاستيكية وتجميع كل شيء ، ينتهي بنا الأمر بنموذج أولي للوحة مفاتيح متسلل مثل هذا:
خاتمة
أحب مقارنة لوحات مفاتيح المطورين بأدوات الموسيقيين. لوحات المفاتيح هي أشياء حميمة إلى حد ما إذا فكرت في الأمر. بعد كل شيء ، نستخدمها طوال اليوم لصياغة برنامج الغد ، حرفًا بحرف.
ربما بسبب ما سبق ، أعتبر تطوير Ultimate Hacking Keyboard امتيازًا ، وعلى الرغم من كل الصعوبات ، فقد كانت في كثير من الأحيان رحلة مثيرة للغاية وتجربة تعليمية مكثفة بشكل لا يصدق.
هذا موضوع واسع ، ولا يمكنني إلا أن أخدش السطح هنا. آمل أن يكون هذا المقال مليئًا بالمرح ومليئًا بالمواد الشيقة. إذا كان لديك أي أسئلة ، فيرجى إبلاغي بذلك في التعليقات.
أخيرًا ، نرحب بك لزيارة https://ultimatehackingkeyboard.com للحصول على مزيد من المعلومات والاشتراك هناك ليتم إعلامك بإطلاق حملتنا.