Спидометр для скейта. Безысходность

в 9:04, , рубрики: android, arduino, cruiserboard, diy или сделай сам, speedometer

Предыстория

Доброго времени суток.
В один прекрасный летний вечер, в поисках чего-нибудь давно забытого, но увлекательного, я перебирал вещи в ящиках. Заглянув в последний, уже слегка отчаявшись, я все-таки нашел интересную вещь. Это был китайский спидометр для велосипеда. Конечно, микрокомпьютера там не было — что не удивительно (в детстве, благодаря моему любопытству, большинство разобранных мною вещей не были собраны и просто напросто были выброшены). Но это не единственная была проблема — у меня нету велосипеда. Его забрал старший брат, а сам я — катаюсь на скейте. Так и возникла идея, чем себя занять!

Под катом много фотографий.

Компоненты

Скейт

Поскольку не выполняю никаких трюков на скейте, я катаюсь на cruiserboard'е в свое удовольствие и бывает интересно: «сколько я проехал?».

Фото cruiserboard'а

Спидометр для скейта. Безысходность

Умные часы

На Новый год приобрел себе у китайцев вот такие часики:

Фото часов

Спидометр для скейта. Безысходность

Это умные часы компании SmartQ, называются Z-Watch. Делятся на старшую (Z1) и младшую (Z1-Lite) модель. Разница в том, что во младшей модели нету: модуля Wi-fi, флэш-памяти eMMC 512Mb (в старшей 4Gb), оперативной памяти 256Mb (в старшей 512Mb). Оснащены часы экраном 1.54-inch TFT LCD с разрешением 240x240 пикселей, процессором Ingenic JZ4775 с частотой 1.0Ghz, Bluetooth 4.0 BLE, Wi-fi модулем IEEE 802.11 b/g/n, акселерометром, водонепроницаемые IP-X7 (3 АТМ), батарея Li-poly на 300мАч, операционная система OS Android 4.4 KitKat (упрощенная).

Arduino Mini Pro

Микроконтроллер Arduino Pro Mini:

Фото Arduino Pro Mini

Спидометр для скейта. Безысходность

Был выбран просто потому что был под рукой. Будет считать кол-во оборотов колеса.

Bluetooth HC-06

Bluetooth модуль с помощью которого мы будет связываться с нашими часами:

Фото Bluetooth модуля

Спидометр для скейта. Безысходность
Датчик с герконом

Датчик с китайского спидометра для велосипеда:

Фото датчика
Спидометр для скейта. Безысходность
Аккумулятор

Аккумулятор Samsung напряжением 3.7В (в действительности 4.11В), емкостью 1000мАч:

Фото аккумулятора

Спидометр для скейта. Безысходность

Сборка

Схема нашего устройства:

Схема спидометра

Спидометр для скейта. Безысходность

Думаю, в комментариях не нуждается. Конечно светодиод стоило подключить через транзистор — тогда света было бы больше, но это не столь важно. Прототип я собрал на макетной плате. Она имеет снизу кусок двухстороннего скотча, на который я прицепил аккумулятор. Обрезать крепление датчика к рулю велосипеда я не стал, так как в дальнейшем может обзаведусь, соответственно, велосипедом :)

Фото прототипа

Спидометр для скейта. Безысходность
Спидометр для скейта. Безысходность

Датчик с герконом я закрепил на подвеске при помощи жгутиков:

Фото нижней части скейта

Спидометр для скейта. Безысходность

Светодиод снизу был прикреплен для проверки передачи данных от часов к микроконтроллеру. Подойдет в качестве подсветки нижней части ночью.
Просверлил в колесе неглубокое отверстие и закрепил в нем неодимовый магнит (вытащил со старого дисковода):

Фото колеса с магнитом

Спидометр для скейта. Безысходность

Программная часть

В программировании не силен, но буду очень рад в советах по этому поводу!

Микроконтроллер

volatile long cntr;
boolean flip;
boolean yes = false;
int rev = 0;

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  pinMode(12, INPUT);
  TCCR2A = 0; 
  TCCR2B = 2;
  TCNT2 = 59;
  TIMSK2 |= (1 << TOIE2); 

}

ISR(TIMER2_OVF_vect) {
  TCNT2 = 59;//55;
  cntr++;
  if(cntr>9999) 
  {
    flip = true;
    cntr = 0;
  }
}

void loop() 
{
  if (flip) 
  {
    Serial.println(String(rev)+';');
    rev = 0;
    flip = false;
  }  
  else
  {
    if (digitalRead(12) == HIGH) 
    {
      if (yes)
      {
        rev++;
        yes = false;
      }
    } else yes = true;
  }
  
  if (Serial.available() > 0){
    char command = Serial.read();
    switch(command){
      case '0': digitalWrite(13, LOW); break;
      case '1': digitalWrite(13, HIGH); break;
    }
  }
}
Суть программы

Пока таймер тикает, в основном цикле подсчитывается кол-во оборотов и обрабатываются команды с часов, если таковые имеются. Через секунду контроллер отправляет кол-во оборотов на часы, т.е. мы имеем частоту (об/c).

Часы

Код был взят с этой статьи и немного дописан.

Код

AndroidManifest.xml

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
FullscreenActivity.java

package com.example.admin.speedometer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;

import com.example.admin.speedometer.R;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.bluetooth.*;
import android.content.Intent;

public class FullscreenActivity extends Activity {
    private static final int REQUEST_ENABLE_BT = 1;
    final int ArduinoData = 1;
    final String LOG_TAG = "myLogs";
    private BluetoothAdapter btAdapter = null;
    private BluetoothSocket btSocket = null;
    private StringBuilder sb = new StringBuilder();
    private static String MacAddress = "20:13:05:07:01:97"; // MAC-адрес БТ модуля
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private ConnectedThred MyThred = null;
    public TextView spdtext, distext, fromarduino;
    public double Distance = 0;
    Button b1, b2;
    Handler h;

    /*
    Settings:
     */
    private static double Radius = 3.0; //радиус колеса в сантиметрах
    private static double spdUnit = 3.6; //размерность скорости: 3.6 в км/ч, 1.0 в м/c

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fullscreen);

        btAdapter = BluetoothAdapter.getDefaultAdapter();
        spdtext = (TextView) findViewById(R.id.textView1);
        distext = (TextView) findViewById(R.id.textView2);
        fromarduino = (TextView) findViewById(R.id.textView5);

        if (btAdapter != null){
            if (btAdapter.isEnabled()){
                //mytext.setText("Bluetooth включен. Все отлично.");
            }else
            {
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
            }

        }else
        {
            MyError("Fatal Error", "Bluetooth ОТСУТСТВУЕТ");
        }
        b1 = (Button) findViewById(R.id.button1);
        b2 = (Button) findViewById(R.id.button2);

        b1.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                MyThred.sendData("1");
            }
        });

        b2.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                MyThred.sendData("0");
            }
        });

        h = new Handler() {
            public void handleMessage(android.os.Message msg) {
                switch (msg.what) {
                    case ArduinoData:
                        byte[] readBuf = (byte[]) msg.obj;
                        String strIncom = new String(readBuf, 0, msg.arg1);
                        sb.append(strIncom);
                        int endOfLineIndex = sb.indexOf("rn");
                        if (endOfLineIndex > 0) {
                            String sbprint = sb.substring(0, endOfLineIndex);
                            sb.delete(0, sb.length());
                            String value = "";
                            byte channel = 0; //используется если команд несколько: 0;0;0;
                            fromarduino.setText("Arduino: " + sbprint);
                            for (int i = 0; i < sbprint.length(); i++) {
                                if (sbprint.charAt(i) == ';')  {
                                    if (!value.isEmpty()) {
                                        switch (channel) {
                                            case 0:
                                                double Dis = (Double.parseDouble(value) * (Radius * 6.28) )  / 100.0;
                                                double Speed = Dis * spdUnit;
                                                spdtext.setText(String.valueOf(Math.round(Speed)));
                                                Distance += Dis;
                                                distext.setText(String.valueOf(Math.round(Distance)));
                                                break;
                                        }
                                    }
                                    value = "";
                                    channel++;
                                } else value += sbprint.charAt(i);
                            }
                        }
                        break;
                }
            };
        };

    }

    @Override
    public void onResume() {
        super.onResume();

        BluetoothDevice device = btAdapter.getRemoteDevice(MacAddress);
        Log.d(LOG_TAG, "***Получили удаленный Device***"+device.getName());

        try {
            btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
            Log.d(LOG_TAG, "...Создали сокет...");
        } catch (IOException e) {
            MyError("Fatal Error", "В onResume() Не могу создать сокет: " + e.getMessage() + ".");
        }

        btAdapter.cancelDiscovery();
        Log.d(LOG_TAG, "***Отменили поиск других устройств***");

        Log.d(LOG_TAG, "***Соединяемся...***");
        try {
            btSocket.connect();
            Log.d(LOG_TAG, "***Соединение успешно установлено***");
        } catch (IOException e) {
            try {
                btSocket.close();
            } catch (IOException e2) {
                MyError("Fatal Error", "В onResume() не могу закрыть сокет" + e2.getMessage() + ".");
            }
        }

        MyThred = new ConnectedThred(btSocket);
        MyThred.start();
    }

    @Override
    public void onPause() {
        super.onPause();

        Log.d(LOG_TAG, "...In onPause()...");

        if (MyThred.status_OutStrem() != null) {
            MyThred.cancel();
        }

        try     {
            btSocket.close();
        } catch (IOException e2) {
            MyError("Fatal Error", "В onPause() Не могу закрыть сокет" + e2.getMessage() + ".");
        }
    }

    private void MyError(String title, String message){
        Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();
        finish();
    }


    //Отдельный поток для передачи данных
    private class ConnectedThred extends Thread{
        private final BluetoothSocket copyBtSocket;
        private final OutputStream OutStrem;
        private final InputStream InStrem;

        public ConnectedThred(BluetoothSocket socket){
            copyBtSocket = socket;
            OutputStream tmpOut = null;
            InputStream tmpIn = null;
            try{
                tmpOut = socket.getOutputStream();
                tmpIn = socket.getInputStream();
            } catch (IOException e){}

            OutStrem = tmpOut;
            InStrem = tmpIn;
        }

        public void run()
        {
            byte[] buffer = new byte[1024];
            int bytes;

            while(true){
                try{
                    bytes = InStrem.read(buffer);
                    h.obtainMessage(ArduinoData, bytes, -1, buffer).sendToTarget();
                }catch(IOException e){break;}

            }

        }

        public void sendData(String message) {
            byte[] msgBuffer = message.getBytes();
            Log.d(LOG_TAG, "***Отправляем данные: " + message + "***"  );

            try {
                OutStrem.write(msgBuffer);
            } catch (IOException e) {}
        }

        public void cancel(){
            try {
                copyBtSocket.close();
            }catch(IOException e){}
        }

        public Object status_OutStrem(){
            if (OutStrem == null){return null;
            }else{return OutStrem;}
        }
    }
}

Суть программы

Программа принимает данные с микроконтроллера о частоте вращения колеса. Для того, чтобы получать корректную информацию, нужно измерить радиус колеса, тогда программа найдет его длину окружности и будет рассчитывать скорость и дистанцию. Нужно настроить радиус колеса и в каких единицах будет отображаться скорость:

/*
Settings:
*/
private static double Radius = 3.0; //радиус колеса в сантиметрах
private static double spdUnit = 3.6; //размерность скорости: 3.6 в км/ч, 1.0 в м/c

N — кол-во оборотов;
l — длина окружности;
t — время (поскольку мы считаем раз в секунду — этим значением можно пренебречь);
l = 2пr — длина окружности;
S = V * t = (N * l) / 100 — расстояние которое мы проехали за 1 секунду (выражено в метрах);

double Dis = (Double.parseDouble(value) * (Radius * 6.28) )  / 100.0;

V = S / t = S * 3.6 — скорость (выражена в км/ч).

double Speed = Dis * spdUnit;

Так же имеются две кнопки вкл. и выкл. светодиода, который находится на нижней части скейта.

Вывод

Есть некоторые ошибки, но в целом результатом доволен. Спасибо sychidze за статью!
Видео работы будет немного позже.

Автор: Im_Alive

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js