Предыстория
Доброго времени суток.
В один прекрасный летний вечер, в поисках чего-нибудь давно забытого, но увлекательного, я перебирал вещи в ящиках. Заглянув в последний, уже слегка отчаявшись, я все-таки нашел интересную вещь. Это был китайский спидометр для велосипеда. Конечно, микрокомпьютера там не было — что не удивительно (в детстве, благодаря моему любопытству, большинство разобранных мною вещей не были собраны и просто напросто были выброшены). Но это не единственная была проблема — у меня нету велосипеда. Его забрал старший брат, а сам я — катаюсь на скейте. Так и возникла идея, чем себя занять!
Под катом много фотографий.
Компоненты
Скейт
Поскольку не выполняю никаких трюков на скейте, я катаюсь на 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:
Был выбран просто потому что был под рукой. Будет считать кол-во оборотов колеса.
Bluetooth HC-06
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