جهاز قائم على Arduino يتم التحكم فيه بواسطة جهاز Android عبر Bluetooth - رمز التطبيق و mk (الجزء 2)

عن الجزء الأول


في الجزء الأول ، وصفت الجزء المادي من البناء وقطعة صغيرة من الكود. الآن فكر في مكون البرنامج - تطبيق 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); //  //    setContentView(R.layout.activity_main); //    Button buttonStartFind = (Button) findViewById(R.id.button_start_find); // layout,       listView = (ListView) findViewById(R.id.list_device); //    buttonStartFind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //   ( ) if(permissionGranted()) { //    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if(bluetoothEnabled()) { //   ( ) findArduino(); //   ( ) } } } }); //      buttonStartControl = (Button) findViewById(R.id.button_start_control); buttonStartControl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //     Intent intent = new Intent(); //    intent.setClass(getApplicationContext(), ActivityControl.class); //  ,    startActivity(intent); } }); } 

تتحقق الوظائف أدناه مما إذا تم الحصول على إذن لاستخدام البلوتوث (بدون إذن المستخدم لن نتمكن من نقل البيانات) وما إذا كانت البلوتوث قيد التشغيل:

 private boolean permissionGranted() { //   ,  true if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH) == PermissionChecker.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_ADMIN) == PermissionChecker.PERMISSION_GRANTED) { return true; } else { ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN}, 0); return false; } } private boolean bluetoothEnabled() { //  ,  true,  ,      if(bluetoothAdapter.isEnabled()) { return true; } else { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 0); return false; } } 

إذا تم تمرير جميع عمليات التحقق ، فسيبدأ البحث عن الجهاز. إذا لم يتم استيفاء أحد الشروط ، فسيتم عرض إشعار يقول "السماح \ تمكين؟" ، وسيتكرر هذا حتى يتم تمرير الشيك.

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

 private void findArduino() { //    Set<BluetoothDevice> pairedDevice = bluetoothAdapter.getBondedDevices(); if (pairedDevice.size() > 0) { //     pairedDeviceArrayList = new ArrayList<>(); //  for(BluetoothDevice device: pairedDevice) { //      //: " /" pairedDeviceArrayList.add(device.getAddress() + "/" + device.getName()); } } //  ,    item_device.xml pairedDeviceAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_device, R.id.item_device_textView, pairedDeviceArrayList); listView.setAdapter(pairedDeviceAdapter); //      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { //    String itemMAC = listView.getItemAtPosition(i).toString().split("/", 2)[0]; //      BluetoothDevice connectDevice = bluetoothAdapter.getRemoteDevice(itemMAC); try { // socket - ,      Method m = connectDevice.getClass().getMethod( "createRfcommSocket", new Class[]{int.class}); clientSocket = (BluetoothSocket) m.invoke(connectDevice, 1); clientSocket.connect(); if(clientSocket.isConnected()) { //  ,   bluetoothAdapter.cancelDiscovery(); } } catch(Exception e) { e.getStackTrace(); } } }); } 

عندما يتم العثور على وحدة بلوتوث معلقة على Arduino (المزيد عن هذا لاحقًا) ، ستظهر في القائمة. بالنقر فوقه ، ستبدأ في إنشاء مأخذ (قد تضطر إلى الانتظار 3-5 ثوان بعد النقر أو النقر مرة أخرى). ستدرك أن الاتصال يتم من خلال مصابيح LED في وحدة Bluetooth: بدون اتصال ، فإنها تومض بسرعة ، إذا كان هناك اتصال ، ينخفض ​​التردد بشكل ملحوظ.


قم بإدارة الأوامر وإرسالها


بعد إنشاء الاتصال ، يمكنك المتابعة إلى النشاط الثاني - ActivityControl. لن يكون هناك سوى دائرة زرقاء على الشاشة - عصا التحكم. وهي مصنوعة من الزر المعتاد ، وترد العلامات أعلاه.

 public class ActivityControl extends AppCompatActivity { //,   private Button buttonDriveControl; private float BDCheight, BDCwidth; private float centerBDCheight, centerBDCwidth; private String angle = "90"; //0, 30, 60, 90, 120, 150, 180 private ConnectedThread threadCommand; private long lastTimeSendCommand = System.currentTimeMillis(); } 

في طريقة onCreate () ، تتم جميع الإجراءات الرئيسية:

 //        performClick() //    @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) { //  super.onCreate(savedInstanceState); // ,    setContentView(R.layout.activity_control); //  buttonDriveControl = (Button) findViewById(R.id.button_drive_control); //    final ViewTreeObserver vto = buttonDriveControl.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //      (!) BDCheight = buttonDriveControl.getHeight(); BDCwidth = buttonDriveControl.getWidth(); //    (!) centerBDCheight = BDCheight/2; centerBDCwidth = BDCwidth/2; // GlobalListener,     buttonDriveControl.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); // ,     //    buttonDriveControl.setOnTouchListener(new ControlDriveInputListener()); //  ,      //    ,     //    threadCommand = new ConnectedThread(MainActivity.clientSocket); threadCommand.run(); } 

انتبه (!) - سنكتشف عدد وحدات البكسل التي يشغلها الزر. بفضل هذا ، نحصل على القدرة على التكيف: سيعتمد حجم الزر على دقة الشاشة ، لكن بقية الكود سيتكيف بسهولة مع هذا ، لأننا لا نقوم بإصلاح الأحجام مسبقًا. في وقت لاحق ، سنقوم بتعليم التطبيق لمعرفة مكان اللمس ، ثم ترجمته إلى قيم يمكن فهمها لـ 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) { //     //      (!) final float x = motionEvent.getX(); final float y = motionEvent.getY(); //,     switch(motionEvent.getAction()) { //  //  ,      case MotionEvent.ACTION_DOWN: //  timer = new Timer(); //  // :    0, //  10  timer.schedule(new TimerTask() { @Override public void run() { //   calculateAndSendCommand(x, y); } }, 0, 10); break; //    (  ACTION_DOWN) case MotionEvent.ACTION_MOVE: // (!) //     Timer(),   if(timer != null) { timer.cancel(); timer = null; } //   timer = new Timer(); //     ,    ACTION_UP timer.schedule(new TimerTask() { @Override public void run() { calculateAndSendCommand(x, y); } }, 0, 10); break; //     case MotionEvent.ACTION_UP: //  if(timer != null) { timer.cancel(); timer = null; } break; } return false; } } 

انتبه مرة أخرى: تؤدي طريقة x و y على طريقة Touch () إلى الوصول من الزاوية العلوية اليسرى للعرض. في حالتنا ، تقع النقطة (0 ؛ 0) في Button هنا:



الآن بعد أن تعلمنا كيفية الحصول على الموقع الحالي للإصبع على الأزرار ، سنكتشف كيفية تحويل وحدات البكسل (لأن x و y هما المسافة بالبكسل فقط) إلى قيم العمل. للقيام بذلك ، استخدم طريقة calculateAndSendCommand (x، y) ، والتي يجب وضعها في فئة ControlDriveInputListener. ستحتاج أيضًا إلى بعض الطرق المساعدة ، نكتبها في نفس الفصل بعد calculateAndSendCommand (x، y).

 private void calculateAndSendCommand(float x, float y) { //    //   // - 1, 2, 3, 4 // ,   ,      // ,     ,     int quarter = identifyQuarter(x, y); //       // y,        int speed = speedCalculation(centerBDCheight - y); //   //   ,    7   String angle = angleCalculation(x); //     ,     //      ,      /*String resultDown = "x: "+ Float.toString(x) + " y: " + Float.toString(y) + " qr: " + Integer.toString(quarter) + "\n" + "height: " + centerBDCheight + " width: " + centerBDCwidth + "\n" + "speed: " + Integer.toString(speed) + " angle: " + angle; */ //viewResultTouch.setText(resultDown); //  ,    //      (  ),   100  if((System.currentTimeMillis() - lastTimeSendCommand) > 100) { //   threadCommand.sendCommand(Integer.toString(speed), angle); //     lastTimeSendCommand = System.currentTimeMillis(); } } private int identifyQuarter(float x, float y) { //,      //  if(x > centerBDCwidth && y > centerBDCheight) { return 4; } else if (x < centerBDCwidth && y >centerBDCheight) { return 3; } else if (x < centerBDCwidth && y < centerBDCheight) { return 2; } else if (x > centerBDCwidth && y < centerBDCheight) { return 1; } return 0; } private int speedCalculation(float deviation) { //  //      float coefficient = 255/(BDCheight/2); //    //   int speed = Math.round(deviation * coefficient); //    70,    // ,    ,    if(speed > 0 && speed < 70) speed = 0; if(speed < 0 && speed > - 70) speed = 0; //     120 // ,     if(speed < 120 && speed > 70) speed = 120; if(speed > -120 && speed < -70) speed = -120; //     , ACTION_MOVE   //    ,     //      if(speed > 255 ) speed = 255; if(speed < - 255) speed = -255; //:  > 0 -  , < 0 -  return speed; } private String angleCalculation(float x) { //    7  //0 -  , 180 -  //90 -    if(x < BDCwidth/6) { angle = "0"; } else if (x > BDCwidth/6 && x < BDCwidth/3) { angle = "30"; } else if (x > BDCwidth/3 && x < BDCwidth/2) { angle = "60"; } else if (x > BDCwidth/2 && x < BDCwidth/3*2) { angle = "120"; } else if (x > BDCwidth/3*2 && x < BDCwidth/6*5) { angle = "150"; } else if (x > BDCwidth/6*5 && x < BDCwidth) { angle = "180"; } else { angle = "90"; } return angle; } 

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

توجد فئة ConnectedThread أيضًا في فئة ActivityControl.

 private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) { //  this.socket = btSocket; //  -       OutputStream os = null; try { os = socket.getOutputStream(); } catch(Exception e) {} outputStream = os; } public void run() { } public void sendCommand(String speed, String angle) { //    ,   byte[] speedArray = speed.getBytes(); byte[] angleArray = angle.getBytes(); //    //  ,  ,       String a = "#"; String b = "@"; String c = "*"; try { outputStream.write(b.getBytes()); outputStream.write(speedArray); outputStream.write(a.getBytes()); outputStream.write(c.getBytes()); outputStream.write(angleArray); outputStream.write(a.getBytes()); } catch(Exception e) {} } } 

تلخيص تطبيق Android


لخص بإيجاز كل مرهق أعلاه.

  1. في ActivityMain نقوم بتكوين البلوتوث ، نقوم بإنشاء اتصال.
  2. في ActivityControl نرفق الزر ونحصل على بيانات عنه.
  3. نعلق على زر OnTouchListener ، فإنه يمسك باللمس والحركة ورفع الإصبع.
  4. يتم تحويل البيانات التي تم الحصول عليها (النقطة بإحداثيات س وص) إلى زاوية الدوران والسرعة
  5. نرسل البيانات ، ونفصل بينها بأحرف خاصة

وسيأتي إليك الفهم النهائي عندما تنظر إلى الرمز بالكامل - 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 إذا كانت الزاوية صغيرة جدًا ، ولكن هذا خرقاء إلى حد ما في رأيي.

نتائج رسم


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

في النهاية


جرد كامل لعدة أشهر من العمل قد انتهى. تم تفكيك الجزء المادي ، البرنامج أكثر. يبقى المبدأ كما هو - الاتصال لظواهر غير مفهومة ، سوف نفهمها معًا.

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

نتيجة الفيديو


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


All Articles