دواسة على الأرض: إنشاء آخر مناور القدم للكمبيوتر


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

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

لماذا أحتاجه؟


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

في جميع الأوقات ، حاول المبرمجون والمصممون إنشاء واجهة مريحة وسهلة الاستخدام بحيث يمكن للمستخدم العمل مع التطبيق باستخدام الماوس ولوحة المفاتيح دون مشاكل غير ضرورية ، فلماذا نحتاج إلى معالج آخر؟ حسنًا ، دعونا ننظر قليلاً في التاريخ ، أو بالأحرى ، في بداية القرن الثامن عشر ، عندما تم اختراع آلة موسيقية مثل البيانو. كما تعلمون ، تُترجم هذه الكلمة حرفيًا "بصوت عالٍ وهادئ" ، لكن قلة من الناس يعتقدون أن سيدًا إيطاليًا ذكيًا تلقى مثل هذه الأداة ، وفي الواقع "استغل" قيثارة القيثارة الموجودة آنذاك ، والتي مكنت من التحكم في مستوى الصوت إلى حد ما ، دون أخذ يديه بعيدا عن المفاتيح.

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

بشكل عام ، آمل أن أقنعكم ، وهذا يعني أن الوقت قد حان للبدء مباشرة في التطوير نفسه.

الموارد المطلوبة


  • في الواقع ، الدواسات. نشأت بعض الصعوبات على الفور بسبب حقيقة أنني لم أستطع التفكير في اسم لمثل هذه الدواسة. كنت أعرف فقط أن مثل هذه الأشياء تستخدم في آلات الخياطة. بشكل عام ، بناءً على طلب الدواسة الكهربائية ، ما زلت أتمكن من العثور على ما أحتاجه في Aliexpress ، وبدون التفكير مرتين ، طلبت 3 قطع.
  • المراقب المالي. يجب أن تحاكي لوحة الدواسة لوحة المفاتيح ، وربما ، الماوس لتتمكن من الاتصال بجهاز كمبيوتر دون وجود برامج تشغيل غير ضرورية. لهذا ، فإن لوحة Arduino Pro Micro مثالية ، والتي رغم أنها لا تحتوي على بعض الاستنتاجات ، فهي مصنوعة بأقصى قدر ممكن من الدقة. نذهب إلى نفس Aliexpress ، وشراء النسخة الصينية من هذه المعجزة.
  • الأسلاك. لوضع 3 دواسات أسفل الطاولة ، تحتاج إلى سلك من أربعة أسلاك على الأقل بطول متر واحد على الأقل. هنا ، أعتقد ، لا ينبغي أن تنشأ المشاكل.
  • الصمام RGB والزر. الأول مطلوب للإشارة إلى الأوضاع ، والثاني هو تبديلها.
  • حسنًا ، بالطبع ، نحن بحاجة إلى IDE من Arduino وحديد لحام وذراع مستقيم.

مخطط الجهاز


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



بالنسبة للدواسات ، قررت تخصيص أربعة منافذ PB1-PB4 في آن واحد ، أي اثنين لليسار واثنان للساق اليمنى ، على الرغم من أنني لدي حتى الآن دواسات 3. بالإضافة إلى ذلك ، كلهم ​​في نفس المجموعة ويوجدون في مكان واحد. تحت LED ، أخذت المخرجات PD0 ، PD1 و PD4 ، تحت الزر - PD7.
في هذه الحالة ، لسنا بحاجة إلى أي مقاومات سحب ، إذا كنت تستخدم تلك المدمجة في وحدة التحكم. صحيح ، عند الضغط على زر أو دواسة ، فإن المدخلات ستكون منخفضة ، وعند إطلاقها ، ستكون مرتفعة ، أي ، سيتم قلب المطابع ، ويجب ألا تنسى ذلك.

كتابة التعليمات البرمجية


كانت هذه المرحلة الأصعب: بسبب عدة أخطاء في المؤشرات ، قمت بمسح أداة تحميل التشغيل عدة مرات وكنتيجة لذلك فشلت في جعل اللوحة على مستوى البرنامج. أدناه جميع مراحل إنشاء البرنامج الثابت موصوفة بالتفصيل ، لأولئك الذين يرغبون فقط في الحصول على كود عمل ، سيكون في نهاية المقال.

تدريب


نحتاج أولاً إلى فهم ماهية الدواسة من حيث البرنامج. قررت أن أجعل من الممكن ضبط دواسات أحد وضعين - الوقت الفعلي والزناد. في نفس الوقت ، يكون لكل دواسة برنامجان: الأول يتم تنفيذه عندما يتم وضع الدواسة في الوقت الفعلي أو مع الضغط الغريب في وضع المشغل ، والثاني هو عندما يتم تحرير الدواسة في الوقت الحقيقي أو عندما يتم ضغط الدواسات بالتساوي في وضع الزناد. يحتوي الدواسة أيضًا على منفذ وحالة ومتغيرين - المواضع الحالية في البرنامجين 1 و 2. حصلت على هذا الهيكل:

struct pedal { char port; //   char state; //  ,   char oldState; //  ,   char pos1; //  1 char pos2; //  2 unsigned char type; //0 —   , 1 —  ; unsigned char act1[16]; // 1 unsigned char act2[16]; // 2 }; 

لدى اردوينو قدر لا بأس به من الذاكرة و 8 بت ، لذلك من الأفضل محاولة استخدام char بدلاً من int حيثما كان ذلك ممكنًا.

نحتاج أيضًا إلى مكتبة لوحة المفاتيح القياسية لكي تعمل كلوحة مفاتيح.

انقر فوق معالجة


نحتاج الآن إلى عمل مترجم يقوم بقراءة البيانات من الصفيف وإرسالها في شكل ضغطات المفاتيح إلى الجهاز ، وكذلك تحديد عدة قيم لمختلف الأوامر الداخلية. نفتح الصفحة مع رموز المفاتيح ، ونرى ماذا وكيف يمكننا النقر. لم أحفر بعمق وأدرس جميع أنواع معايير لوحة المفاتيح ، لأن المعلومات هنا تبدو لي كافية لمثل هذا المشروع. النصف الأول مخصص لأحرف ASCII القياسية (على الرغم من أن بعضها غير قابل للطباعة أو غير مستخدم) ، النصف الثاني مخصص لمفاتيح التعديل المختلفة. توجد حتى رموز منفصلة للمفاتيح اليمنى واليسرى ، والتي تعتبر ممتعة للغاية ، لكنني لم أر أي رموز خاصة للأرقام من nampad ، على الرغم من أنني ، حسب علمي ، يتم إدراكها بطريقة خاصة في النظام مقارنة بالأرقام العادية. ربما تكون أكوادها في مكان ما في "الثقوب" ، بين النطاقات ، ولكن الآن لا يتعلق الأمر بذلك. لذا ، فإن أكبر رمز هو مفتاح الصعود - 218 ، مما يعني أن النطاق من 219 إلى 255 يمكن اعتباره مجانيًا أو جيدًا أو على الأقل لا يوجد أي مفاتيح مهمة.

 void pedalAction() { //255  ,     if (pedal1->type == 255) return; //     unsigned char *prg; //     char *pos; if (pedal1->type) { //       int current; if ((current = digitalRead(ports[num])) != oldState[num]) { if (!current) state[num] = !state[num]; oldState[num] = current; } if (!state[num]) { //act1 pos2[num] = 0; pos = &(pos1[num]); prg = pedal1->act1; } else { //act2 pos1[num] = 0; pos = &(pos2[num]); prg = pedal1->act2; } } else { //        if (!digitalRead(ports[num])) { //act1 pos2[num] = 0; pos = &(pos1[num]); prg = pedal1->act1; } else { //act2 pos1[num] = 0; pos = &(pos2[num]); prg = pedal1->act2; } } while (1) { if (prg[*pos] == 254) { // ,   *pos Keyboard.press(prg[++*pos]); } else if (prg[*pos] == 253) { // ,   *pos Keyboard.release(prg[++*pos]); } else if (prg[*pos] == 252) { //" ",    ++*pos; return; } else if (prg[*pos] == 251) { //       *pos+1 *pos = prg[*pos + 1]; return; } else if (prg[*pos] == 255 || prg[*pos] == 0) { // ,   return; } else { //   Keyboard.write(prg[*pos]); } //       ,     if (++*pos>=16) pos = 0; } } 

أعتقد أنه حتى الشخص الذي ليس لديه أعلى مستوى من المعرفة بـ C لن يكون لديه أسئلة حول ما يحدث هنا. أولاً ، تحدد الوظيفة الدواسة المرغوبة وتحدد ، حسب وضع وحالة الدواسة ، البرنامج الذي يجب تنفيذه. عند قراءة كل عنصر من عناصر الصفيف ، إذا لم يكن حرف التحكم ، يتم استدعاء وظيفة Keyboard.write () ، والتي تحاكي الضغط على المفتاح وتحريره. تتم معالجة أحرف التحكم بشكل منفصل وتحتاج إلى تثبيت مجموعات المفاتيح والتنقل في البرنامج.

بعض ميزات وضع لوحة المفاتيح
يحتوي Keyboard.write () على بعض الفروق الدقيقة ، ولكنها غير واضحة للمبتدئين ، استنادًا إلى حقيقة أننا نرسل البيانات ليس بشكلها الأولي ، ولكن كضغطات المفاتيح. أولاً ، من الغريب أنه بدون برامج تشغيل إضافية ، يمكن للكمبيوتر قبول أحرف من لوحة المفاتيح الموجودة على لوحة المفاتيح فقط ، مما يعني أننا لن نتمكن من إرسال أي 0x03 (إشارة المقاطعة) أو 0x1B (بداية تسلسل ESCAPE). ثانياً ، يمكننا ضبط الأحرف الكبيرة كما هي في جدول ASCII ، لكن الجهاز سيحصل على تركيبة المفاتيح Shift + <أحرف صغيرة>. قد يصبح هذا مشكلة إذا تم تمكين CapsLock وسنتلقى "بشكل غير متوقع" أحرفًا صغيرة بدلاً من الحروف الكبيرة والعكس. ثالثًا ، لا يمكننا استخدام اللغة الروسية ، وأي لغة أخرى. يحدث هذا مرة أخرى بسبب أشياء مزعجة مثل رموز المفاتيح . على الرغم من أن Keyboard.write () يقبلها كوسيطة ، فإن الكود المقابل للمفتاح الموجود عليه في تخطيط اللغة الإنجليزية القياسي لا يزال يتم إرساله عبر USB ، وإذا حاولنا إرسال الأبجدية السيريلية ، فلن نعرف ماذا. لذلك ، إذا أردنا أن نقول مرحبًا لأصدقائنا الناطقين بالروسية من خلال Arduino ، إذن في الكود نحتاج إلى كتابة "Ghbdtn" ، ثم إرساله ، بعد تحديد التصميم الروسي. مثل هذه "التحية" ستعمل وفقًا للتخطيط الأوكراني ، ولكن باللغة البلغارية ، على الرغم من وجود أبجدية السيريلية أيضًا ، فلن يأتي شيء منها ، لأن الحروف الموجودة في أماكن مختلفة تمامًا. (سمعت مرةً رأيًا مفاده أنه بالنسبة للعديد من المطورين الأمريكيين والإنجليز ، من غير المفهوم أن شخصًا ما قد يحتاج حتى إلى استخدام عدة تخطيطات ، ولكن أيضًا تبديلها).

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

 struct pedal *pedal1 = {15, 0, 0, 0, 0, 0, "Hello, world!\0", 0}; void prepare () { pinMode(15, 2); //2 - INPUT_PULLUP,        Keyboard.begin(); } void loop() { pedalAction(); } 

بالمناسبة ، لا تنس أبدًا إنهاء الأجهزة الفارغة في هذه "البرامج" إذا كان طولها أقل من حجم المصفوفة وإذا لم تكن دورية ، لأن Arduino لن يحاول فقط تفسير البيانات التي لم يتم ضبطها ، بل سيرسلها أيضًا إلى الجهاز بسرعة كبيرة ، وهذا هو نفسه إعطاء لوحة مفاتيح للقرود.

دواسة واحدة جيدة ، واثنان أفضل


حان الوقت الآن للتعامل مع معالجة الإشارات من عدة دواسات ، بالإضافة إلى إضافة أوضاع التبديل. في بداية المقال ، تم تخصيص 4 منافذ للدواسات ، يجب السماح لكل منها بالعمل في سبعة أوضاع. لماذا 7؟ لأنه بدون استخدام PWM ، يمكن لمصباحنا أن يعطي 7 ألوان فقط ، والثامن. هذا المبلغ كافٍ للمستخدم العادي ، ولكن في الحالات القصوى يمكن زيادته بسهولة. لذلك سنقوم بتخزين الدواسات في صفيف ثنائي الأبعاد من 7 × 4. لكي لا تسد الذاكرة ، يمكن أخذ القيم المشتركة بين عدة هياكل ، مثل رقم المنفذ ، في صفائف منفصلة. نتيجة لذلك ، حصلنا على شيء مثل هذا:

 struct pedal { unsigned char type; unsigned char act1[16]; unsigned char act2[16]; }; struct pedal pedals[7][4] = { { { 255, {"Hello, world!\0"}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} } }; char ports[4] = {15, 16, 14, 8}; char pos1[4] = {0, 0, 0, 0}; char pos2[4] = {0, 0, 0, 0}; char state[4] = {0, 0, 0, 0}; char oldState[4] = {0, 0, 0, 0}; char mode = 0; //  char curPedal = 0; //   

سحر الرقم 255
ربما لاحظت أنه في المقالة ، غالبًا ما يظهر الرقم 255 ، حيث سيكون من المنطقي وضع 0. وبالنظر إلى المستقبل ، سأقول أن هذا ضروري لتوفير الراحة لتخزين الدواسات في EEPROM ، حيث أن كل خلية من خاناتها من المصنع لا تحتوي على صفر ، ولكن من المصنع فقط 255 ، مما يعني أن هذا الرقم سيكون أكثر ملاءمة للاستخدام للإشارة إلى متغيرات غير محددة من 0 ، بحيث لا تقوم بالكتابة فوق الذاكرة في كل مرة.

من المهم بالنسبة لنا أن نعرف فقط نوع الدواسة والبرنامجين ، لذلك سنتركهم فقط مباشرة في الهيكل ، والسماح للأتمتة بالباقي. ستبدو أساليب الاستعداد والحلقة الآن كما يلي:

 void prepare(){ pinMode(2, 1); pinMode(3, 1); pinMode(4, 1); pinMode(6, 2); for (int i : ports) pinMode(i, 2); Keyboard.begin(); } void loop() { for (int i = 0; i < 6; i++) { int current; if ((current = digitalRead(modeButton)) != last) { if (!current) { if (++mode >= 7) mode = 0; while (pedals[mode][0].type == 255 && pedals[mode][1].type == 255 && pedals[mode][2].type == 255 && pedals[mode][3].type == 255) if (++mode >= 7) { mode = 0; break; } } last = current; digitalWrite(2, (mode + 1) & 0b001); digitalWrite(3, (mode + 1) & 0b010); digitalWrite(4, (mode + 1) & 0b100); for (int i = 0; i < 4; i++) { pos1[i] = 0; pos2[i] = 0; state[i] = 0; oldState[i] = 0; } delay(50); } curPedal = i; pedalAction } } } 

سوف تنظر وحدة التحكم في الوضع غير مستخدم إذا لم يتم الإعلان عن وجود دواسة واحدة فيه (الوضع = 255) ، مما يعني أنه عندما يضربه ، سينتقل على الفور إلى التالي ، ولكن الوضع الأول سيكون دائمًا موجودًا. عند تبديل الوضع ، يتم إلغاء جميع القيم في المصفوفات ، حيث أننا لسنا بحاجة إلى حفظها لكل وضع (يمين؟) ، ثم تتجاوز الحلقة جميع الدواسات وتدعو pedalAction لها.

أيضًا ، في بداية طريقة pedalAction () ، تحتاج إلى إضافة السطر التالي بحيث يفهم أي من الهياكل للتعامل معه:

 struct pedal *pedal1 = &pedals[mode][curPedal]; 

يمكن إزالة هيكل pedal1 الحالي باعتباره غير ضروري.

كل هذا يعمل بشكل جيد أيضًا ، ومع ذلك ، واجهت مشكلة واحدة: بعض البرامج ليس لديها وقت لتلقي النقرات بالسرعة التي يرسلها بها Arduino. الحل الأكثر وضوحا هو إضافة القدرة على تعيين التأخير بين الإجراءات عند الضرورة. فقط عندما نجلس لكتابة برامج لأجهزة التحكم الدقيق ، بقيت جميع الرقائق ، مثل تعدد مؤشرات الأجهزة ، في مكان ما هناك ، في أجهزة الكمبيوتر عالية المستوى ، وعندما نضيف تأخيرًا ، يتوقف البرنامج بالكامل حتى تحسب وحدة التحكم العدد الصحيح من الدورات. نظرًا لعدم وجود تعدد مؤشرات ترابط لدينا ، سنضطر إلى إنشائه.

من الصعب أن أقول نعم من السهل القيام به


لم أخترع دراجة ، لكنني أخذت مكتبة ArduinoThread النهائية. هنا يمكنك قراءة القليل عن كيفية عمله وتنزيله. يمكنك تنزيل المكتبة من Arduino IDE نفسه. باختصار ، يسمح لك بإجراء وظيفة بشكل دوري مع فاصل زمني معين ، مع عدم السماح لك بالذهاب إلى حلقة لانهائية إذا استغرق التنفيذ وقتًا أطول من الفاصل الزمني. ما تحتاجه. إنشاء مجموعة أخرى مع مؤشرات ترابط لكل دواسة:

 Thread pedalThreads[6] = {Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10)}; 

الآن لدينا 6 سلاسل عمليات افتراضية متطابقة ، ولكن في نفس الوقت تكون كائنات مختلفة.

دعنا نعيد كتابة دورة تجاوز الدواسة للعمل مع الوظيفة الجديدة:

 ... for (int i = 0; i < 4; i++) { if (pedalThreads[i].shouldRun()) { curPedal = i; pedalThreads[i].run(); } } ... 

الآن ، ستعطي القيمة 252 في صفيف البرنامج ، والتي تتوافق مع "عدم القيام بأي شيء" ، تأخيرًا قدره 10 ميلي ثانية (على الرغم من أنه أكثر قليلاً بالفعل ، لأن تنفيذ التعليمات البرمجية يستغرق أيضًا وقتًا). تتيح إضافة بضعة أسطر إلى المترجم الشفوي تعيين التأخير في العديد من هذه "الكميات" ، مع إنفاق وحدتي بايت فقط من المصفوفة:

 ... if (wait[num]) { wait[num]--; return; } else if (prg[*pos] == 250) { wait[num] = prg[++*pos]; } ... 

بخلاف الأوامر الأخرى ، يجب إضافة هذه التعليمة في بداية المترجم بالضبط ، أي بعد "بينما (1) {" مباشرة ، حيث يجب معالجة التأخير قبل متابعة المترجم الشفهي لقراءة البرنامج. يجب الإعلان عن صفيف الانتظار بنفس الطريقة التي تم بها مع المنافذ أو الحالة أو ما إلى ذلك. وكذلك إعادة تعيين خلاياها عند تبديل الوضع ، بحيث لا يذهب التأخير إلى برنامج آخر.

الآن ، مع إمكانية تعيين تأخير يصل إلى 2.55 ثانية ، لا ينبغي أن تنشأ مشاكل في تعريف المفاتيح حسب البرامج.

على الحركة والتنقل البرمجة


من حيث المبدأ ، سيكون من الممكن هنا الانتهاء من الشفرة والبدء في تجميع الجهاز ، ولكن في هذه الحالة ، إذا أراد شخص ما إعادة برمجة الدواسات ، فسيتعين عليه فتح ملف Arduino IDE وتعديل الرمز وتنزيل البرنامج الثابت مرة أخرى. بطبيعة الحال ، هذا الخيار ليس هو الأفضل ، لذلك قررت إضافة القدرة على تغيير البرنامج من منفذ Arduino التسلسلي ، وتخزين البرامج نفسها في EEPROM. للعمل مع ذاكرة غير متقلبة ، يجب عليك توصيل المكتبة القياسية EEPROM.h. رمز وضع البرمجة هو كما يلي:

 ... if (!digitalRead(modeButton)) { //  Serial.begin(9600); while (!Serial) { PORTD = 0b00000000 + (PORTD & 0b11101100); delay(250); PORTD = 0b00010000 + (PORTD & 0b11101100); delay(250); } Serial.println(F("***Programming mode***")); Serial.println(F("Write the command as <m> <p> <c>")); Serial.println(F("m - number of mode, one digit")); Serial.println(F("p - number of pedal, one digit")); Serial.println(F("c - command, it can be:")); Serial.println(F("\tr - read pedal info")); Serial.println(F("\tw - enter to writing mode and change pedal programm")); Serial.println(F("\te - erase pedal programm and delete it")); Serial.println(F("There are up to 7 modes and 6 pedals per mode can be configured")); Serial.println(F("Mode will be incative if there is no pedal configured in it")); while (1) { while (Serial.available()) { Serial.read(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(""); Serial.println(F("Enter command")); while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); delay(3); if (Serial.available() == 3) { int curMode = Serial.read() - 48; int curPedal = Serial.read() - 48; char cmd = Serial.read(); if (curMode > 6 || curMode < 0) { Serial.print(F("Mode must be in 0-6. You entered ")); Serial.println(curMode); continue; } if (curPedal > 3 || curPedal < 0) { Serial.print(F("Pedal must be in 0-3. You entered ")); Serial.println(curPedal); continue; } Serial.println(); if (cmd == 'r') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.print("type: "); int curAddress = beginAddress; Serial.println(EEPROM[curAddress++]); Serial.print("act1: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.print("act2: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); } else if (cmd == 'w') { Serial.println(F("Enter type:")); PORTD = 0b00000001 + (PORTD & 0b11101100); while (!Serial.available()); int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); int curAddress = beginAddress; PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[curAddress++] = (char)Serial.parseInt(); PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Enter act1 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.println(F("Enter act2 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Finished, don't forget to verify written data!")); } else if (cmd == 'e') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.println(F("Disabling pedal...")); PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[beginAddress] = 255; PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Pedal disabled")); } } else { Serial.println(F("Incorrect command, please read help above")); } }; } ... 

يتم تفسير ما يفعله هذا الرمز من خلال المساعدة الموجودة فيه: يتم إدخال رقم مسافة لرقم الوضع ، ورقم الدواسة ، وأمر ، يوجد منه 3 - قراءة وكتابة وتنفيذ عملية حذف البرنامج. يتم تخزين جميع البيانات الموجودة على الدواسات واحدة تلو الأخرى في تسلسل من 33 بايت ، أي نوع الدواسة ، وبرنامجين ، ونشغلها 7 * 4 * 33 = 924 من 1024 بايت من EEPROM. رميت خيار استخدام الحجم الديناميكي للدواسات في الذاكرة ، لأنه في هذه الحالة عند إعادة برمجة دواسة واحدة ، سيتعين عليك الكتابة فوق جميع الخلايا تقريبًا ، وهناك عدد محدود من دورات إعادة الكتابة ، لذلك نوصي بالقيام بذلك بأقل قدر ممكن.

ميزات العمل مع EEPROM
أود أيضًا أن ألفت الانتباه إلى سطور النموذج:
  PORTD = 0b00000010 + (PORTD & 0b11101100); ... PORTD = 0b00000001 + (PORTD & 0b11101100); 

بفضل هذه المكتبة ، من وجهة نظر المبرمج ، تعتبر الذاكرة غير المتطايرة صفيفًا عاديًا من أحرف char ، ولكن ، بصفتنا "اردوينو" ، نحتاج أن نفهم أن الكتابة إلى ROM هي عملية صعبة للغاية ، والتي تستغرق ما يقرب من 3 ثوانٍ من وحدة التحكم ، وينصح بعدم مقاطعة هذه العملية. هذا التصميم يجعل الصمام الثنائي يضيء باللون الأحمر أثناء هذه العمليات ، ثم يُرجع اللون الأخضر "الآمن" للخلف.

في وضع تسجيل البرنامج ، يتم الإدخال مباشرة من خلال قيم البايتات في نظام الأرقام العشرية بمسافة. اتضح بشكل كبير ، ولكن ليس عليك كتابة محلل معقد. علاوة على ذلك ، لا تتم إعادة البرمجة في كثير من الأحيان ، وفي هذه الحالات من الممكن أن ننظر إلى جدول ASCII.

مع الحفاظ على الهياكل المصنفة ، نحتاج الآن إلى سحب بياناتنا بطريقة ما من هناك وتحويلها إلى طريقة عرض "دواسة":

 ... for (int i = 0; i < 7; i++) { for (int j = 0; j < 4; j++) { struct pedal *p = &pedals[i][j]; int beginAddress = sizeof(struct pedal) * (i * 6 + j); int curAddress = beginAddress; unsigned char type = EEPROM[curAddress++]; if (type == 0 || type == 1) { p->type = type; for (int k = 0 ; k < 16; k++) { p->act1[k] = EEPROM[curAddress++]; } for (int k = 0 ; k < 16; k++) { p->act2[k] = EEPROM[curAddress++]; } } } } ... 

لا يوجد شيء خارق يحدث هنا أيضًا: وحدة التحكم تقرأ البيانات من الذاكرة وتملأ الهياكل الموجودة بها.

ميزة البرمجة من خلال UART هي أننا لا نحتاج مرة أخرى إلى أي برامج تشغيل خاصة ، لذلك يمكنك ضبط سلوك المعالج حتى من الهاتف.

مظاهرة




شفرة المصدر الكامل


هو هنا
 #include <Keyboard.h> #include <Thread.h> #include <EEPROM.h> #define modeButton 6 struct pedal { unsigned char type; //0 —   , 1 —  , 255 —    unsigned char act1[16]; unsigned char act2[16]; }; struct pedal pedals[7][4] = { { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} } }; char ports[4] = {8, 16, 15, 14}; char pos1[4] = {0, 0, 0, 0}; char pos2[4] = {0, 0, 0, 0}; char state[4] = {0, 0, 0, 0}; char oldState[4] = {0, 0, 0, 0}; char wait[4] = {0, 0, 0, 0}; void pedalAction(); char mode = 0; char curPedal; Thread pedalThreads[6] = {Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10)}; void setup() { pinMode(2, 1); pinMode(3, 1); pinMode(4, 1); pinMode(modeButton, 2); if (!digitalRead(modeButton)) { //  Serial.begin(9600); while (!Serial) { PORTD = 0b00000000 + (PORTD & 0b11101100); delay(250); PORTD = 0b00010000 + (PORTD & 0b11101100); delay(250); } Serial.println(F("***Programming mode***")); Serial.println(F("Write the command as <m> <p> <c>")); Serial.println(F("m - number of mode, one digit")); Serial.println(F("p - number of pedal, one digit")); Serial.println(F("c - command, it can be:")); Serial.println(F("\tr - read pedal info")); Serial.println(F("\tw - enter to writing mode and change pedal programm")); Serial.println(F("\te - erase pedal programm and delete it")); Serial.println(F("There are up to 7 modes and 6 pedals per mode can be configured")); Serial.println(F("Mode will be incative if there is no pedal configured in it")); while (1) { while (Serial.available()) { Serial.read(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(""); Serial.println(F("Enter command")); while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); delay(3); if (Serial.available() == 3) { int curMode = Serial.read() - 48; int curPedal = Serial.read() - 48; char cmd = Serial.read(); if (curMode > 6 || curMode < 0) { Serial.print(F("Mode must be in 0-6. You entered ")); Serial.println(curMode); continue; } if (curPedal > 3 || curPedal < 0) { Serial.print(F("Pedal must be in 0-3. You entered ")); Serial.println(curPedal); continue; } Serial.println(); if (cmd == 'r') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.print("type: "); int curAddress = beginAddress; Serial.println(EEPROM[curAddress++]); Serial.print("act1: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.print("act2: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); } else if (cmd == 'w') { Serial.println(F("Enter type:")); PORTD = 0b00000001 + (PORTD & 0b11101100); while (!Serial.available()); int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); int curAddress = beginAddress; PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[curAddress++] = (char)Serial.parseInt(); PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Enter act1 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.println(F("Enter act2 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Finished, don't forget to verify written data!")); } else if (cmd == 'e') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.println(F("Disabling pedal...")); PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[beginAddress] = 255; PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Pedal disabled")); } } else { Serial.println(F("Incorrect command, please read help above")); } }; } for (int i : ports) pinMode(i, 2); pinMode(17, 1); for (int i = 0; i < 7; i++) { for (int j = 0; j < 4; j++) { struct pedal *p = &pedals[i][j]; int beginAddress = sizeof(struct pedal) * (i * 6 + j); int curAddress = beginAddress; unsigned char type = EEPROM[curAddress++]; if (type == 0 || type == 1) { p->type = type; for (int k = 0 ; k < 16; k++) { p->act1[k] = EEPROM[curAddress++]; } for (int k = 0 ; k < 16; k++) { p->act2[k] = EEPROM[curAddress++]; } } } } Keyboard.begin(); } int last = 0; void loop() { int current; if ((current = digitalRead(modeButton)) != last) { if (!current) { if (++mode >= 7) mode = 0; while (pedals[mode][0].type == 255 && pedals[mode][1].type == 255 && pedals[mode][2].type == 255 && pedals[mode][3].type == 255) if (++mode >= 7) { mode = 0; break; } } last = current; digitalWrite(2, (mode + 1) & 0b001); digitalWrite(3, (mode + 1) & 0b010); digitalWrite(4, (mode + 1) & 0b100); for (int i = 0; i < 4; i++) { pos1[i] = 0; pos2[i] = 0; state[i] = 0; oldState[i] = 0; wait[i] = 0; } delay(50); } for (int i = 0; i < 4; i++) { if (pedalThreads[i].shouldRun()) { curPedal = i; pedalThreads[i].run(); } } } void pedalAction() { struct pedal *pedal1 = &pedals[mode][curPedal]; if (pedal1->type == 255) return; unsigned char *prg; char *pos; if (pedal1->type) { int current; if ((current = digitalRead(ports[curPedal])) != oldState[curPedal]) { if (!current) state[curPedal] = !state[curPedal]; oldState[curPedal] = current; } if (!state[curPedal]) { //act1 pos2[curPedal] = 0; pos = &(pos1[curPedal]); prg = pedal1->act1; } else { //act2 pos1[curPedal] = 0; pos = &(pos2[curPedal]); prg = pedal1->act2; } } else { if (!digitalRead(ports[curPedal])) { //act1 pos2[curPedal] = 0; pos = &(pos1[curPedal]); prg = pedal1->act1; } else { //act2 pos1[curPedal] = 0; pos = &(pos2[curPedal]); prg = pedal1->act2; } } while (1) { if (wait[curPedal]) { wait[curPedal]--; return; } else if (prg[*pos] == 250) { wait[curPedal] = prg[++*pos]; } else if (prg[*pos] == 254) { // ,   *pos Keyboard.press(prg[++*pos]); } else if (prg[*pos] == 253) { // ,   *pos Keyboard.release(prg[++*pos]); } else if (prg[*pos] == 252) { delay(10); //" ",    ++*pos; return; } else if (prg[*pos] == 251) { //       *pos+1 *pos = prg[*pos + 1]; return; } else if (prg[*pos] == 255 || prg[*pos] == 0) { // ,   return; } else { //   Keyboard.write(prg[*pos]); } //       ,     if (++*pos >= 16) pos = 0; } } 


خاتمة


على الرغم من أنني قمت في البداية باستخدام لوحة دواسة لإمكانية التمرير في التسجيل أثناء العزف على الجيتار ، إلا أنني شخصيا وجدت أنه من المناسب استخدام الدواسات في المهام العادية ، لكن الشيء الرئيسي هو أن أعتاد قليلاً على مثل هذا التلاعب غير المعتاد. وهنا توجد مشكلة أخرى: بدون الدواسات المفضلة ، يصبح العمل على العكس أكثر صعوبة ، حيث يجب عليك أن تتذكر ماذا وأين ولماذا الضغط. إذا كان لا يزال من الممكن ارتداء الدواسات وتوصيلها بالمكتب ، فمن الصعب في هذه المكان أن تتجول معهم في الفصول الدراسية. لذا ، فإن استخدام هذا الجهاز لشيء آخر غير الغرض الأصلي هو على مسؤوليتك وحدك.

تجميعها pedalboard:

Source: https://habr.com/ru/post/ar462263/


All Articles