عن الجزء الأول
في الجزء الأول ، وصفت الجزء المادي من البناء وقطعة صغيرة من الكود. الآن فكر في مكون البرنامج - تطبيق Android ورسم اردوينو.
أولاً ، سأقدم وصفًا تفصيليًا لكل لحظة ، وفي النهاية سأترك روابط للمشروع بأكمله + فيديو للنتيجة ، الأمر الذي
سيخيب أملك.
تطبيق Android
ينقسم برنامج Android إلى قسمين: الأول هو توصيل الجهاز عبر Bluetooth ، والثاني هو عصا التحكم.
أحذرك - لم يتم تصميم التطبيق على الإطلاق وتم إجراؤه على خطأ ، إذا نجح فقط. لا تنتظر القدرة على التكيف و UX ، ولكن لا ينبغي أن تخرج من الشاشة.
التخطيط
يعتمد نشاط البدء على التخطيط والعناصر: الأزرار والتخطيط لقائمة من الأجهزة. يبدأ الزر عملية البحث عن الأجهزة التي تعمل بتقنية Bluetooth النشطة. يعرض ListView الأجهزة التي تم العثور عليها.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="60dp" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_marginStart="40dp" android:layout_marginTop="50dp" android:text="@string/start_search" android:id="@+id/button_start_find" /> <Button android:layout_width="wrap_content" android:layout_height="60dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:id="@+id/button_start_control" android:text="@string/start_control" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true"/> <ListView android:id="@+id/list_device" android:layout_width="300dp" android:layout_height="200dp" android:layout_marginEnd="10dp" android:layout_marginTop="10dp" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" /> </RelativeLayout>
تعتمد شاشة التحكم على تخطيط ، حيث لا يوجد سوى زر ، والذي سيصبح في المستقبل عصا تحكم. يتم إرفاق زر بالزر من خلال سمة الخلفية ، مما يجعله دائريًا.
لا يتم استخدام TextView في الإصدار النهائي ، ولكن تمت إضافته في الأصل لتصحيح الأخطاء: تم عرض الأرقام المرسلة عبر البلوتوث. في المرحلة الأولية ، أنصحك باستخدام. ولكن بعد ذلك سيبدأ حساب الأرقام في دفق منفصل ، حيث يصعب الوصول إلى TextView.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="200dp" android:layout_height="200dp" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:layout_marginBottom="25dp" android:layout_marginStart="15dp" android:id="@+id/button_drive_control" android:background="@drawable/button_control_circle" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" android:minWidth="70dp" android:id="@+id/view_result_touch" android:layout_marginEnd="90dp" /> </RelativeLayout>
ملف button_control_circle.xml (النمط) ، يجب وضعه في المجلد القابل للرسم:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#00F" /> <corners android:bottomRightRadius="100dp" android:bottomLeftRadius="100dp" android:topRightRadius="100dp" android:topLeftRadius="100dp"/> </shape>
تحتاج أيضًا إلى إنشاء ملف item_device.xml ، فهو ضروري لكل عنصر قائمة:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="150dp" android:layout_height="40dp" android:id="@+id/item_device_textView"/> </LinearLayout>
البيان
فقط في حالة ، سأعطي كود البيان الكامل. تحتاج إلى الحصول على حق الوصول الكامل إلى البلوتوث عبر استخدام الإذن ولا تنسَ الإشارة إلى النشاط الثاني من خلال علامة النشاط.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.bluetoothapp"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.arproject.bluetoothworkapp.MainActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.arproject.bluetoothworkapp.ActivityControl" android:theme="@style/Theme.AppCompat.NoActionBar" android:screenOrientation="landscape"/> </application> </manifest>
النشاط الرئيسي ، إقران Arduino و Android
نحن نرث الصف من AppCompatActivity ونعلن المتغيرات:
public class MainActivity extends AppCompatActivity { private BluetoothAdapter bluetoothAdapter; private ListView listView; private ArrayList<String> pairedDeviceArrayList; private ArrayAdapter<String> pairedDeviceAdapter; public static BluetoothSocket clientSocket; private Button buttonStartControl; }
سأصف طريقة onCreate () سطرًا بسطر:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
تتحقق الوظائف أدناه مما إذا تم الحصول على إذن لاستخدام البلوتوث (بدون إذن المستخدم لن نتمكن من نقل البيانات) وما إذا كانت البلوتوث قيد التشغيل:
private boolean permissionGranted() {
إذا تم تمرير جميع عمليات التحقق ، فسيبدأ البحث عن الجهاز. إذا لم يتم استيفاء أحد الشروط ، فسيتم عرض إشعار يقول "السماح \ تمكين؟" ، وسيتكرر هذا حتى يتم تمرير الشيك.
ينقسم البحث عن الجهاز إلى ثلاثة أجزاء: إعداد القائمة ، والإضافة إلى قائمة الأجهزة التي تم العثور عليها ، وإنشاء اتصال بالجهاز المحدد.
private void findArduino() {
عندما يتم العثور على وحدة بلوتوث معلقة على Arduino (المزيد عن هذا لاحقًا) ، ستظهر في القائمة. بالنقر فوقه ، ستبدأ في إنشاء مأخذ (قد تضطر إلى الانتظار 3-5 ثوان بعد النقر أو النقر مرة أخرى). ستدرك أن الاتصال يتم من خلال مصابيح LED في وحدة Bluetooth: بدون اتصال ، فإنها تومض بسرعة ، إذا كان هناك اتصال ، ينخفض التردد بشكل ملحوظ.

قم بإدارة الأوامر وإرسالها
بعد إنشاء الاتصال ، يمكنك المتابعة إلى النشاط الثاني - ActivityControl. لن يكون هناك سوى دائرة زرقاء على الشاشة - عصا التحكم. وهي مصنوعة من الزر المعتاد ، وترد العلامات أعلاه.
public class ActivityControl extends AppCompatActivity {
في طريقة onCreate () ، تتم جميع الإجراءات الرئيسية:
انتبه (!) - سنكتشف عدد وحدات البكسل التي يشغلها الزر. بفضل هذا ، نحصل على القدرة على التكيف: سيعتمد حجم الزر على دقة الشاشة ، لكن بقية الكود سيتكيف بسهولة مع هذا ، لأننا لا نقوم بإصلاح الأحجام مسبقًا. في وقت لاحق ، سنقوم بتعليم التطبيق لمعرفة مكان اللمس ، ثم ترجمته إلى قيم يمكن فهمها لـ arduinki من 0 إلى 255 (بعد كل شيء ، يمكن أن تكون اللمس 456 بكسل من المركز ، ولن تعمل MK مع هذا الرقم).
التالي هو رمز ControlDriveInputListener () ، هذه الفئة تقع في فئة النشاط نفسه ، بعد طريقة onCreate (). كونها في ملف ActivityControl ، تصبح فئة ControlDriveInputListener تابعة ، مما يعني أنه يمكنه الوصول إلى جميع متغيرات الفئة الرئيسية.
لا تنتبه إلى الوظائف التي يتم استدعاؤها عند النقر فوقها. نحن الآن مهتمون بعملية التقاط اللمسات: في أي نقطة يضع الشخص إصبعه وما هي البيانات التي سنحصل عليها.
يرجى ملاحظة أنني أستخدم فئة java.util.Timer: فهي تسمح لك بإنشاء سلسلة رسائل جديدة قد يكون لها تأخير وستتكرر بعدد لا نهائي من المرات بعد كل عدد من الثواني. يجب استخدامها في الحالة التالية: وضع الشخص إصبعًا ، وعملت طريقة ACTION_DOWN ، وذهبت المعلومات إلى اردوينو ، وبعد ذلك قرر الشخص عدم تحريك الإصبع ، لأن السرعة تناسبه. في المرة الثانية ، لن تعمل طريقة ACTION_DOWN ، لأنك تحتاج أولاً إلى الاتصال بـ ACTION_UP (لرفع إصبعك من الشاشة).
حسنًا ، نبدأ حلقة حلقة Timer () ونبدأ في إرسال نفس البيانات كل 10 مللي ثانية. عند إزاحة الإصبع (ستعمل ACTION_MOVE) أو رفعها (ACTION_UP) ، يجب قتل دورة المؤقت حتى لا يبدأ إرسال البيانات من الصحافة القديمة مرة أخرى.
public class ControlDriveInputListener implements View.OnTouchListener { private Timer timer; @Override public boolean onTouch(View view, MotionEvent motionEvent) {
انتبه مرة أخرى: تؤدي طريقة x و y على طريقة Touch () إلى الوصول من الزاوية العلوية اليسرى للعرض. في حالتنا ، تقع النقطة (0 ؛ 0) في Button هنا:

الآن بعد أن تعلمنا كيفية الحصول على الموقع الحالي للإصبع على الأزرار ، سنكتشف كيفية تحويل وحدات البكسل (لأن x و y هما المسافة بالبكسل فقط) إلى قيم العمل. للقيام بذلك ، استخدم طريقة calculateAndSendCommand (x، y) ، والتي يجب وضعها في فئة ControlDriveInputListener. ستحتاج أيضًا إلى بعض الطرق المساعدة ، نكتبها في نفس الفصل بعد calculateAndSendCommand (x، y).
private void calculateAndSendCommand(float x, float y) {
عندما يتم حساب البيانات ونقلها ، يدخل التيار الثاني في اللعبة. هو المسؤول عن إرسال المعلومات. لا يمكنك الاستغناء عنها ، وإلا فإن بيانات نقل المقبس سوف تبطئ التقاط اللمسات ، وسيتم إنشاء قائمة انتظار وستكون النهاية بأكملها أقصر.
توجد فئة ConnectedThread أيضًا في فئة ActivityControl.
private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) {
تلخيص تطبيق Android
لخص بإيجاز كل مرهق أعلاه.
- في ActivityMain نقوم بتكوين البلوتوث ، نقوم بإنشاء اتصال.
- في ActivityControl نرفق الزر ونحصل على بيانات عنه.
- نعلق على زر OnTouchListener ، فإنه يمسك باللمس والحركة ورفع الإصبع.
- يتم تحويل البيانات التي تم الحصول عليها (النقطة بإحداثيات س وص) إلى زاوية الدوران والسرعة
- نرسل البيانات ، ونفصل بينها بأحرف خاصة
وسيأتي إليك الفهم النهائي عندما تنظر إلى الرمز بالكامل -
github.com/IDolgopolov/BluetoothWorkAPP.git . لا يوجد رمز تعليق ، لذلك يبدو أكثر نظافة وأصغر وأبسط.
رسم اردوينو
تم تفكيك تطبيق Android ، وكتابته ، وفهمه ... وهنا سيكون الأمر أسهل. سأحاول النظر في كل شيء على مراحل ، ثم سأعطي رابطًا للملف الكامل.
المتغيرات
أولاً ، ضع في الاعتبار الثوابت والمتغيرات التي ستحتاج إليها.
#include <SoftwareSerial.h> // \ // SoftwareSerial BTSerial(8, 9); // int speedRight = 6; int dirLeft = 3; int speedLeft = 11; int dirRight = 7; // , int angleDirection = 4; int angleSpeed = 5; //, , // int pinAngleStop = 12; // String val; // int speedTurn = 180; //, // int pinRed = A0; int pinWhite = A1; int pinBlack = A2; // long lastTakeInformation; //, , boolean readAngle = false; boolean readSpeed = false;
طريقة الإعداد ()
في طريقة الإعداد () ، نقوم بتعيين معلمات الدبابيس: ستعمل على الإدخال أو الإخراج. قمنا أيضًا بتعيين سرعة اتصال الكمبيوتر مع اردوينو ، وبلوتوث مع اردوينو.
void setup() { pinMode(dirLeft, OUTPUT); pinMode(speedLeft, OUTPUT); pinMode(dirRight, OUTPUT); pinMode(speedRight, OUTPUT); pinMode(pinRed, INPUT); pinMode(pinBlack, INPUT); pinMode(pinWhite, INPUT); pinMode(pinAngleStop, OUTPUT); pinMode(angleDirection, OUTPUT); pinMode(angleSpeed, OUTPUT); // HC-05 // , BTSerial.begin(38400); // Serial.begin(9600); }
طريقة Loop () ووظائف إضافية
تتم قراءة البيانات في طريقة loop () المتكررة باستمرار. أولاً ، ضع في اعتبارك الخوارزمية الرئيسية ، ثم الوظائف المضمنة فيها.
void loop() { // if(BTSerial.available() > 0) { // char a = BTSerial.read(); if (a == '@') { // @ ( ) // val val = ""; //, readSpeed = true; } else if (readSpeed) { // // val if(a == '#') { // , // Serial.println(val); //, readSpeed = false; // go(val.toInt()); // val val = ""; // , return; } val+=a; } else if (a == '*') { // readAngle = true; } else if (readAngle) { // , // , val if(a == '#') { Serial.println(val); Serial.println("-----"); readAngle = false; // turn(val.toInt()); val= ""; return; } val+=a; } // lastTakeInformation = millis(); } else { // , 150 // if(millis() - lastTakeInformation > 150) { lastTakeInformation = 0; analogWrite(angleSpeed, 0); analogWrite(speedRight, 0); analogWrite(speedLeft, 0); } } }
نحصل على النتيجة: من الهاتف نرسل وحدات بايت بأسلوب "@ speed # angle #" (على سبيل المثال ، أمر نموذجي "@ 200 # 60 #". تتكرر هذه الدورة كل 100 مللي ثانية ، حيث نعين هذا الفاصل الزمني في نظام Android لإرسال الأوامر. باختصار لا معنى له ، نظرًا لأنها تبدأ في الطابور ، وإذا جعلتها أطول ، تبدأ العجلات في التحرك رعشة ، فكلالتأخيرات من خلال أمر delay () ، الذي ستراه لاحقًا ، لا يتم تحديده من خلال الحسابات الفيزيائية والرياضية ، ولكن تجريبيًا. zadrezham ، تسير الآلة بسلاسة ، وتدخل جميع الفرق لديها وقت للعمل (التيارات لديها الوقت للتشغيل).يتم استخدام وظيفتين جانبيتين في الدورة ، وهما يأخذان البيانات المستلمة ويجعلان الآلة تدور وتدور. void go(int mySpeed) { // 0 if(mySpeed > 0) { // digitalWrite(dirRight, HIGH); analogWrite(speedRight, mySpeed); digitalWrite(dirLeft, HIGH); analogWrite(speedLeft, mySpeed); } else { // 0, digitalWrite(dirRight, LOW); analogWrite(speedRight, abs(mySpeed) + 30); digitalWrite(dirLeft, LOW); analogWrite(speedLeft, abs(mySpeed) + 30); } delay(10); } void turn(int angle) { // digitalWrite(pinAngleStop, HIGH); // , delay(5); // 150 , // 30 , // 31 149 if(angle > 149) { // , // , // return if( digitalRead(pinWhite) == HIGH && digitalRead(pinBlack) == LOW && digitalRead(pinRed) == LOW) { return; } // // digitalWrite(angleDirection, HIGH); analogWrite(angleSpeed, speedTurn); } else if (angle < 31) { if(digitalRead(pinRed) == HIGH && digitalRead(pinBlack) == HIGH && digitalRead(pinWhite) == HIGH) { return; } digitalWrite(angleDirection, LOW); analogWrite(angleSpeed, speedTurn); } // digitalWrite(pinAngleStop, LOW); delay(5); }
اقلب عندما يرسل Android البيانات التي قام المستخدم بتثبيتها بزاوية 60 ، 90 ، 120 ، لا يستحق ذلك ، وإلا فلن تتمكن من الذهاب مباشرة. نعم ، ربما لا يجب عليك إرسال أمر منعطف على الفور من android إذا كانت الزاوية صغيرة جدًا ، ولكن هذا خرقاء إلى حد ما في رأيي.نتائج رسم
يحتوي الرسم التخطيطي على ثلاث خطوات مهمة فقط: قراءة أمر ، ومعالجة حدود الدوران ، وتزويد التيار للمحركات. يبدو كل شيء بسيطًا ، وفي التنفيذ أسهل من السهل ، على الرغم من أنه تم إنشاؤه لفترة طويلة وبتأثيرات. النسخة الكاملة للرسم .في النهاية
جرد كامل لعدة أشهر من العمل قد انتهى. تم تفكيك الجزء المادي ، البرنامج أكثر. يبقى المبدأ كما هو - الاتصال لظواهر غير مفهومة ، سوف نفهمها معًا.والتعليقات الواردة في الجزء الأول مثيرة للاهتمام ، فقد نصحوا جبلًا من النصائح المفيدة ، وذلك بفضل الجميع.نتيجة الفيديو