The machine on the Arduino, controlled by an Android device via Bluetooth, is the application code and mic (part 2)

    About the first part

    In the first part I described the physical part of the construction and only a small piece of code. Now consider the software component - an application for Android and an Arduino sketch.

    First I will give a detailed description of each moment, and at the end I will leave links to the projects as a whole + a video of the result, which should disappoint you to encourage.

    Android application

    The program for android is divided into two parts: the first is the connection of the device via Bluetooth, the second is the control joystick.

    I warn you - the design of the application was not worked out at all and was done in any way, just to work. Adaptability and UX do not wait, but should not get out of the screen.


    Starting activity is kept on layout, elements: buttons and layout for a list of devices. The button starts the process of finding devices with active Bluetooth. ListView displays found devices.

    <?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"

    The control screen is based on the layout, in which there is only a button that in the future will become a joystick. To the button, through the background attribute, a style is attached that makes it round.
    TextView is not used in the final version, but initially it was added for debugging: digits were sent via Bluetooth. At the initial stage I advise to use. But then the numbers will begin to be calculated in a separate stream, from which it is difficult to access the TextView.

    <?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android=""android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid: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" /><TextViewandroid: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"

    File button_control_circle.xml (style), it must be placed in the folder drawable:

    <?xml version="1.0" encoding="utf-8"?><shapexmlns:android=""android:shape="rectangle"><solidandroid:color="#00F" /><cornersandroid:bottomRightRadius="100dp"android:bottomLeftRadius="100dp"android:topRightRadius="100dp"android:topLeftRadius="100dp"/></shape>

    You also need to create an item_device.xml file, it is needed for each item in the list:

    <?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android=""android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="150dp"android:layout_height="40dp"android:id="@+id/item_device_textView"/></LinearLayout>


    Just in case, give the full code manifest. It is necessary to get full access to bluetooth through uses-permission and not to forget to designate the second activity through the activity tag.

    <?xml version="1.0" encoding="utf-8"?><manifestxmlns:android=""package="com.example.bluetoothapp"><uses-permissionandroid:name="android.permission.BLUETOOTH" /><uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" /><applicationandroid: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"><activityandroid:name="com.arproject.bluetoothworkapp.MainActivity"android:theme="@style/Theme.AppCompat.NoActionBar"android:screenOrientation="landscape"><intent-filter><actionandroid:name="android.intent.action.MAIN" /><categoryandroid:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name="com.arproject.bluetoothworkapp.ActivityControl"android:theme="@style/Theme.AppCompat.NoActionBar"android:screenOrientation="landscape"/></application></manifest>

    Main activity, pairing Arduino and Android

    Inherit the class from AppCompatActivity and declare variables:

            private BluetoothAdapter bluetoothAdapter;
            private ListView listView;
            private ArrayList<String> pairedDeviceArrayList;
            private ArrayAdapter<String> pairedDeviceAdapter;
            publicstatic BluetoothSocket clientSocket;
            private Button buttonStartControl;

    I will describe the onCreate () method line by line:

    @OverrideprotectedvoidonCreate(Bundle savedInstanceState){
         super.onCreate(savedInstanceState); //обязательная строчка//прикрепляем ранее созданную разметку
         //цепляем кнопку из разметки          
         Button buttonStartFind = (Button) findViewById(; 
         //цепляем layout, в котором будут отображаться найденные устройства
         listView = (ListView) findViewById(; 
         //устанавливаем действие на клик                                                                           
         buttonStartFind.setOnClickListener(new View.OnClickListener() { 
             @OverridepublicvoidonClick(View v){
                 //если разрешения получены (функция ниже)if(permissionGranted()) { 
                   //адаптер для управления блютузом
                    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 
                    if(bluetoothEnabled()) { //если блютуз включен (функция ниже)
                        findArduino(); //начать поиск устройства (функция ниже)
         //цепляем кнопку для перехода к управлению
         buttonStartControl = (Button) findViewById(; 
         buttonStartControl.setOnClickListener(new View.OnClickListener() {
             @OverridepublicvoidonClick(View v){
                    //объект для запуска новых активностей
                    Intent intent = new Intent(); 
                    //связываем с активностью управления
                    intent.setClass(getApplicationContext(), ActivityControl.class);
                    //закрыть эту активность, открыть экран управления

    The following functions check whether permission to use bluetooth is obtained (without the user's permission, we will not be able to transmit data) and whether bluetooth is enabled:

         //если оба разрешения получены, вернуть trueif (ContextCompat.checkSelfPermission(getApplicationContext(),
              Manifest.permission.BLUETOOTH) == PermissionChecker.PERMISSION_GRANTED &&
              ContextCompat.checkSelfPermission(getApplicationContext(),                   Manifest.permission.BLUETOOTH_ADMIN) == PermissionChecker.PERMISSION_GRANTED) {
         } else {
              ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.BLUETOOTH,
                      Manifest.permission.BLUETOOTH_ADMIN}, 0);
    //если блютуз включен, вернуть true, если нет, вежливо попросить пользователя его включитьif(bluetoothAdapter.isEnabled()) {
         } else {
             Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
             startActivityForResult(enableBtIntent, 0);

    If all checks are passed, the device search begins. If one of the conditions is not met, the notification will be displayed, they say, “allow \ enable?”, And this will be repeated until the check is passed.

    Device search is divided into three parts: preparing a list, adding devices to the list of found devices, establishing a connection with the selected device.

       //получить список доступных устройств 
       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,, pairedDeviceArrayList); 
        //на каждый элемент списка вешаем слушатель
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @OverridepublicvoidonItemClick(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);
               if(clientSocket.isConnected()) {
                    //если соединение установлено, завершаем поиск
               } catch(Exception e) {

    When the Bluetooth module hung on the Arduino (more on this later) is found, it will appear in the list. By clicking on it, you will start creating a socket (perhaps after a click you will have to wait 3-5 seconds or click again). You will understand that the connection is established, by the LEDs on the Bluetooth module: without a connection, they flash quickly, and if there is a connection, the frequency decreases noticeably.

    Manage and send commands

    After the connection is established, you can proceed to the second activity - ActivityControl. On the screen there will be only a blue circle - a joystick. It is made from the usual Button, the markup is shown above.

        //переменные, которые понадобятсяprivate Button buttonDriveControl;
        privatefloat BDCheight, BDCwidth;
        privatefloat centerBDCheight, centerBDCwidth;
        private String angle = "90"; //0, 30, 60, 90, 120, 150, 180private ConnectedThread threadCommand;
        privatelong lastTimeSendCommand = System.currentTimeMillis();

    In the onCreate () method, all the main actions take place:

    //без этой строки студия потребует вручную переопределить метод performClick()//нам оно не недо@SuppressLint("ClickableViewAccessibility") 
    @OverrideprotectedvoidonCreate(Bundle savedInstanceState){
        //обязательная строкаsuper.onCreate(savedInstanceState);
        //устанавливаем разметку, ее код выше
        //привязываем кнопку
        buttonDriveControl = (Button) findViewById(;
        //получаем информацию о кнопке final ViewTreeObserver vto = buttonDriveControl.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    //получаем высоту и ширину кнопки в пикселях(!)
                    BDCheight = buttonDriveControl.getHeight();
                    BDCwidth = buttonDriveControl.getWidth();
                    //находим центр кнопки в пикселях(!)
                    centerBDCheight = BDCheight/2;
                    centerBDCwidth = BDCwidth/2;
                    //отключаем GlobalListener, он больше не понадобится 
            //устанавливаем листенер, который будет отлавливать прикосновения //его код представлен ниже
            buttonDriveControl.setOnTouchListener(new ControlDriveInputListener());
            //создаем новый поток, он будет занят отправкой данных//в качестве параметра передаем сокет, созданный в первой активности //код потока представлен ниже
            threadCommand = new ConnectedThread(MainActivity.clientSocket);

    Pay attention (!) - we will find out how many pixels the button takes. Due to this, we get adaptability: the size of the button will depend on the screen resolution, but the rest of the code is easy to adjust to it, because we do not fix the dimensions in advance. Later we will teach the application to find out where the touch was, and then translate it into values ​​that are understandable for Arduinka from 0 to 255 (after all, the touch may be 456 pixels from the center, and the MK will not work with that number).

    The following is the code ControlDriveInputListener (), this class is located in the class of the activity itself, after the onCreate () method. Being in the ActivityControl file, the ControlDriveInputListener class becomes a child, which means it has access to all variables of the main class.

    Do not pay attention to the functions called by pressing for now. Now we are interested in the process of catching touches: at what point a person has put a finger and what data we will get about it.

    Please note that I use the java.util.Timer class: it allows you to create a new stream, which can be delayed and repeated an infinite number of times in each number of seconds. It should be used for the following situation: the person put his finger, the ACTION_DOWN method worked, the information went to Arduinka, and after that the person decided not to move his finger, because he is satisfied with the speed. The second time the ACTION_DOWN method does not work, because you first need to call ACTION_UP (pull your finger off the screen).

    Well, we start the Timer () class loop and start sending the same data every 10 milliseconds. When the finger is moved (ACTION_MOVE will work) or raised (ACTION_UP), the Timer cycle must be killed so that the data from the old click does not start to be sent again.

        private Timer timer;
        @OverridepublicbooleanonTouch(View view, MotionEvent motionEvent){
         //получаем точки касания в пикселях //отсчет ведется от верхнего левого угла (!)finalfloat x = motionEvent.getX();
         finalfloat y = motionEvent.getY();
          //узнаем, какое действие было сделаноswitch(motionEvent.getAction()) {
              //если нажатие //оно сработает всегда, когда вы дотронетесь до кнопкиcase MotionEvent.ACTION_DOWN:
                    //создаем таймер
                    timer = new Timer();
                    //запускаем цикл//аргументы указывают: задержка между повторами 0, //повторять каждые 10 миллисекунд
                    timer.schedule(new TimerTask() {
                               //функцию рассмотрим ниже
                                calculateAndSendCommand(x, y);
                     }, 0, 10);
                //если палец был сдвинут (сработает после ACTION_DOWN)case MotionEvent.ACTION_MOVE:
                    //обязательно (!)//если ранее был запущен цикл Timer(), завершаем егоif(timer != null) {
                         timer = null;
                     //создаем новый цикл
                     timer = new Timer();
                     //отправляем данные с той же частотой, пока не сработает ACTION_UP
                     timer.schedule(new TimerTask() {
                             calculateAndSendCommand(x, y);
                     }, 0, 10);
                //если палец убрали с экранаcase MotionEvent.ACTION_UP:
                     //убиваем циклif(timer != null) {
                         timer = null;

    Please note again: the onTouch () method uses the x and y count from the upper left corner of the View. In our case, the point (0; 0) is at Button here:

    Now that we have learned how to get the current finger position on the buttons, let's figure out how to convert pixels (after all, x and y are exactly the distance in pixels) into working values. To do this, I use the calculateAndSendCommand (x, y) method, which should be placed in the ControlDriveInputListener class. You will also need some auxiliary methods; we write them to the same class after calculateAndSendCommand (x, y).

    privatevoidcalculateAndSendCommand(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();
            privateintidentifyQuarter(float x, float y){
                //смотрим, как расположена точка относительно центра//возвращаем уголif(x > centerBDCwidth && y > centerBDCheight) {
                  } elseif (x < centerBDCwidth && y >centerBDCheight) {
                    } elseif (x < centerBDCwidth && y < centerBDCheight) {
                     } elseif (x > centerBDCwidth && y < centerBDCheight) {
            privateintspeedCalculation(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";
                } elseif (x > BDCwidth/6 && x < BDCwidth/3) {
                    angle = "30";
                } elseif (x > BDCwidth/3 && x < BDCwidth/2) {
                    angle = "60";
                } elseif (x > BDCwidth/2 && x < BDCwidth/3*2) {
                    angle = "120";
                } elseif (x > BDCwidth/3*2 && x < BDCwidth/6*5) {
                    angle = "150";
                } elseif (x > BDCwidth/6*5 && x < BDCwidth) {
                    angle = "180";
                } else {
                    angle = "90";
                return angle;

    When the data is calculated and transferred, a second stream enters the game. He is responsible for sending information. You cannot do without it, otherwise the socket transmitting the data will slow down the catching of touches, a queue will be created and the whole end will be shorter.

    The ConnectedThread class is also located in the ActivityControl class.

            privatefinal BluetoothSocket socket;
            privatefinal OutputStream outputStream;
            publicConnectedThread(BluetoothSocket btSocket){
                //получаем сокетthis.socket = btSocket;
                //создаем стрим - нить для отправки данных на ардуино 
                OutputStream os = null;
                try {
                    os = socket.getOutputStream();
                } catch(Exception e) {}
                outputStream = os;
            publicvoidsendCommand(String speed, String angle){
                //блютуз умеет отправлять только байты, поэтому переводимbyte[] speedArray = speed.getBytes();
                byte[] angleArray = angle.getBytes();
                //символы используются для разделения//как это работает, вы поймете, когда посмотрите принимающий код скетча ардуино
                String a = "#";
                String b = "@";
                String c = "*";
                try {
                } catch(Exception e) {}

    Summing up the Android application

    Briefly summarize all the bulky above.

    1. In ActivityMain we set up bluetooth, establish a connection.
    2. In ActivityControl we bind a button and get data about it.
    3. We hang on the OnTouchListener button, it catches touch, movement and lifting of a finger.
    4. The obtained data (a point with x and y coordinates) is converted into a rotation angle and speed
    5. We send data, separating them with special characters.

    A final understanding will come to you when you look at the entire code - . There is a code without comments, so it looks much cleaner, smaller and simpler.

    Sketch Arduino

    The Android application is disassembled, written, understood ... and here it will be easier. I will try to consider everything step by step, and then I will give a link to the full file.


    To begin, consider the constants and variables that will be needed.

    #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;

    Setup () method

    In the setup () method, we set the parameters of the pins: they will work as input or output. Also set the speed of communication of the computer with arduinka, bluetooth with arduinka.

    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
      //если у вас модуль другой версии, смотрите документацию
      //эта скорость постоянна 

    The loop () method and additional functions

    In the constantly repeating loop () method, data is read. First, consider the main algorithm, and then the functions involved in it.

    void loop() {
      //если хоть несчитанные байтыif(BTSerial.available() > 0) {
         //считываем последний несчитанный байт
         char a =;
        if (a == '@') {
          //если он равен @ (случайно выбранный мною символ)//обнуляем переменную valval = "";
          //указываем, что сейчас считаем скорость
          readSpeed = true;
        } elseif (readSpeed) {
          //если пора считывать скорость и байт не равен решетке//добавляем байт к valif(a == '#') {
            //если байт равен решетке, данные о скорости кончились//выводим в монитор порта для отладки
            //указываем, что скорость больше не считываем
            readSpeed = false;
            //передаем полученную скорость в функцию езды 
            //обнуляем valval = "";
            //выходим из цикла, чтобы считать следующий байтreturn;
        } elseif (a == '*') {
          //начинаем считывать угол поворота
          readAngle = true; 
        } elseif (readAngle) {
          //если решетка, то заканчиваем считывать угол//пока не решетка, добавляем значение к valif(a == '#') {
            readAngle = false;
            //передаем значение в функцию поворота
            val= "";
        //получаем время последнего приема данных
        lastTakeInformation = millis();
      } else {
         //если несчитанных байтов нет, и их не было больше 150 миллисекунд //глушим двигателиif(millis() - lastTakeInformation > 150) {
         lastTakeInformation = 0;
         analogWrite(angleSpeed, 0);
         analogWrite(speedRight, 0);
         analogWrite(speedLeft, 0);

    We get the result: from the phone we send bytes in the style "@ speed # angle #" (for example, a typical command "@ 200 # 60 #". This cycle repeats every 100 milliseconds, since on the android we set this interval for sending commands. In short, do it makes no sense, since they will start to queue, and if you make it longer, the wheels will start to move in jerks.

    All delays through the delay () command, which you will see later, are chosen not through physical and mathematical calculations, but through experience. Thanks to all zadrejam, the machine goes smoothly, and in All the teams have time to work out (the currents have time to run.)

    The cycle uses two side functions, they accept the data and make the machine go and spin.

    voidgo(int mySpeed){
      //если скорость больше 0if(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);
    voidturn(int angle){
      //подаем ток на плюс определителя угла
      digitalWrite(pinAngleStop, HIGH);
      //даем задержку, чтобы ток успел установиться
      //если угол 150 и больше, поворачиваем вправо //если 30 и меньше, то влево //промежуток от 31 до 149 оставляем для движения прямоif(angle > 149) {
            //если замкнут белый, но разомкнуты  черный и красный//значит достигнуто крайнее положение, дальше крутить нельзя//выходим из функции через return if( digitalRead(pinWhite) == HIGH && digitalRead(pinBlack) == LOW && digitalRead(pinRed) == LOW) {
            //если проверка на максимальный угол пройдена//крутим колеса
            digitalWrite(angleDirection, HIGH);
            analogWrite(angleSpeed, speedTurn);
      } elseif (angle < 31) { 
            if(digitalRead(pinRed) == HIGH && digitalRead(pinBlack) == HIGH && digitalRead(pinWhite) == HIGH) {
            digitalWrite(angleDirection, LOW);
            analogWrite(angleSpeed, speedTurn);
      //убираем питание 
      digitalWrite(pinAngleStop, LOW);

    Turning, when the android sends data that the user has clamped the angle 60, 90, 120, is not worth it, otherwise you will not be able to go straight. Yes, maybe you shouldn't have immediately sent a command to turn from the android, if the angle is too small, but this is somehow clumsy in my opinion.

    Sketch results

    The sketch has only three important steps: reading the command, handling rotation restrictions and applying current to the motors. Everything sounds simple, and in the performance is easier than easy, although it was created for a long time and with blunders. The full version of the sketch .


    A full inventory of several months of work is over. The physical part is disassembled, the software is even more so. The principle remains the same - contact for incomprehensible phenomena, we will understand together.

    And the comments under the first part are interesting, advised a mountain of useful tips, thanks to everyone.

    Result Video

    Also popular now: