[Arduino+Android] 自制土砲智能安全帽 / 智慧安全帽

專案動機(1/2)

現今社會中,各種交通運輸載具方便了人類的生活,縮小了地域的差異性,其中車輛是人們日常生活中最頻繁接觸到的一部分。
車輛使人們的行動更加便利,也因此道路上行駛的車輛越來越多。
路況的複雜性也因此增加,行車安全的問題日趨重要。
道路上危險事故的產生,除了是由於駕駛人本身道路安全觀念不足造成,也可能是由於駕駛人本身對車輛的控制力不夠而造成危險。

專案動機(2/2)

在駕駛人不具備職業車手之技能與意識的前提下,要維持對車體良好安定的控制,最穩妥的條件就是保持安全穩定的車速,以避免不可抗力之意外發生。
因此駕駛人需要能隨時迅速的得知車輛的實際車速,而非單純憑藉人體對速度的感覺來操控車輛。
考慮到二輪機車通常不具備擋風玻璃來實裝擡頭顯示器(HUD)以顯示車速,本專題將實作能顯示即時車速的智慧安全帽,減少視線轉移至儀表板以及身體重心變化而發生危險的機會。


系統功能

1.以行動裝置得到GPS定位數據並算出即時車速
2.於安全帽中顯示即時車速
3.車速超過70km時發出危險警報



硬體清單

1.四分之三安全帽 * 1
2.Arduino UNO * 1
3.模組(HC-06從機*1
4.Android手機 * 1
5.74HC595串列式七段顯示器模組 * 1
6.蜂鳴器 *1
7.USB(Type A)母接頭 *1
8.鏡面反射膜 *1
9.各種線材與廢物利用


GPS測速

利用行動裝置的GPS定位根據車輛行駛中不斷變化位置,持續得到某兩個點的位置數據以及兩點之間的時間差來計算出行車速度數據。(採用Android API)

在衛星訊號穩定、車輛穩定行駛、路面斜率不大的情況下,GPS所測得速度幾乎等於真實速度。


GPS測速與儀表板之誤差

在實際測試中,車輛穩定行駛的情況下,當車速維持3040km/h時,測試用機車之儀表板所顯示速度比GPS定位測得之車速平均高7  km/h左右。
原因:
         根據臺灣交通部車輛安全檢測基準,以無載條件(空車重+駕駛員+必要儀器)且依下列速率測試,指示速率必須永不少於真實速率且速率計標度盤指示之速率(V1)與真實速率(V2)間應滿足關係:

          0 <= ( V1 - V2 ) <= ( V2 / 10) +4 km/h

故車速越快,在允許範圍內的誤差就可能越大。



即時車速顯示

行動裝置上之Android程式計算出的車速通過藍芽模組將數據傳送給Arduino
採用74HC595串列式七段顯示器模組顯示數據,於安全帽內(考慮到防)投射速度數據於反射膜以反射給眼睛。


上方圖示爲HC-06藍芽模組(左)、74HC595串列式七段顯示器模組(右)




上圖爲安全帽內部效果展示



反射投影

當物體距離眼睛太近,便無法準確使物體在視網膜上清晰成像。
爲了能清楚看到物體,必須延長物距。
安全帽內空間有限,鏡面反射成像是簡單有效的方案。

透過反射投影,在安全帽有限空間內理想視距最大可達到兩倍





系統架構




裝置電路圖




遭遇問題

由於安全帽擋風鏡非平面,若將反射膜直接貼於擋風鏡上,以反射式投影會使影像看起來不平整。
解決方式:
         爲反射膜製作獨立基座,使反射膜保持平整,並與視線儘可能保持垂直。





裝置組裝完成圖




實際展示影片

Youtube:

http://youtu.be/qU20tuYm1ZA


優酷:

http://v.youku.com/v_show/id_XNzI5MzE2Njcy.html


(由於反射膜爲半透明非全反射,會透光,因此從外部所看到七段顯示器的數字會是顛倒的。)


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


源代碼

最後附上本專案所用到的源代碼,工程我就不打包了,下面已經幾乎包括全部的東西


Android 行動裝置端主程序

package com.helmethud;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

public class HelmetHud extends Activity {

	// 除錯用標記
	private static final String TAG = "HELMETHUDCLIENT";
	private static final boolean D = true;

	// Bluetooth相關變量
	private BluetoothAdapter mBluetoothAdapter = null;
	private BluetoothSocket btSocket = null;
	private OutputStream outStream = null;
	// Well known SPP UUID (will *probably* map to
	// RFCOMM channel 1 (default) if not in use);
	// see comments in onResume().
	private static final UUID MY_UUID = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");

	// ==> hardcode bluetooth server's MAC address here <==
	// private static String address = "98:D3:31:40:03:34";
	private boolean BTIsConnected = false; // 藍芽是否已連線flag
	private boolean hasFirstMatch = false; // 藍芽是否已經過第一次配對flag
	private boolean ReceiverIsRegisted = false; // BroadcastReceiver是否已註冊flag

	// 線程HANDLE相關變量
	private Handler connectBTHandler = new Handler();
	private Handler sendBTMsgHandler = new Handler();
	private Handler sendBTTestMsgHandler = new Handler();
	private Handler hudThreadHandler = new Handler();
	private Handler getSpeedThreadHandler = new Handler();
	Handler msgHandler;

	// UI相關變量
	private Button btnDiscovery, btnConnect, btnSend, btnStartHud;
	private Spinner spinner;
	private ArrayAdapter<String> arrayAdapterDeviceName;
	private static final String[][] devicesStr = { { "Plz select device...", "" } };
	private List<String> allDevices, allDevicesAddress;
	private int selectedAddress = 0;

	// GPS定位相關變量
	final String SERVERNAME = Context.LOCATION_SERVICE;
	LocationManager locationManager;
	float speed = 0, lastSpeed = 0; // 車速
	int satelliteCount = 0; // 當前衛星個數
	Date gpdDate = new Date();
	StringBuilder tempBuilder = new StringBuilder();
	TextView tvGPSState, tvGPSInfo;
	private static final int STATIC_CONTINUED_TIME = 6000; // 判斷靜止持續時間(毫秒)

	EditText et1; // 測試用輸入框

	private boolean IsTesting = false; // 程序執行是否為發送測試訊息
	int sum = 0; // SendMsgThread傳送藍芽訊息線程執行總次數
	private static final int BT_RECONNECT_LIMIT_TIME = 300; // 線程最大允許執行次數;值訂為300以防止低階設備效能不足而出錯
	private static final int NEED_BT_RECONNECT_DEVICE_MAX_SDK = 13; // 需要重新連線的裝置Android_SDK_API最大版本

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_helmet_hud);
		setTitle("Not connected");

		et1 = (EditText) findViewById(R.id.editText1);
		btnDiscovery = (Button) findViewById(R.id.btnDiscovery);
		btnConnect = (Button) findViewById(R.id.btnConnect);
		btnSend = (Button) findViewById(R.id.btnSend);
		spinner = (Spinner) findViewById(R.id.spinner1);
		allDevices = new ArrayList<String>();
		for (int i = 0; i < devicesStr.length; i++) {
			allDevices.add(devicesStr[i][0]);
		}
		allDevicesAddress = new ArrayList<String>();
		for (int i = 0; i < devicesStr.length; i++) {
			allDevicesAddress.add(devicesStr[i][1]);
		}
		/* new ArrayAdapter物件並將allDevices傳入 */
		arrayAdapterDeviceName = new ArrayAdapter<String>(this,
				android.R.layout.simple_spinner_item, allDevices);
		arrayAdapterDeviceName
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		spinner.setAdapter(arrayAdapterDeviceName);
		btnStartHud = (Button) findViewById(R.id.btnStartHud);
		// btnStartHud.setEnabled(false); //測試時保持可用
		tvGPSState = (TextView) findViewById(R.id.tvGPSState);
		tvGPSInfo = (TextView) findViewById(R.id.tvGPSInfo);

		btnDiscovery.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				if (mBluetoothAdapter.isDiscovering()) {
					mBluetoothAdapter.cancelDiscovery();
					Log.e(TAG, "Cancel discovery");
				}

				mBluetoothAdapter.startDiscovery(); // 開始搜尋裝置

			}
		});

		btnConnect.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				connectBTHandler.post(ConnectBTThread);
			}
		});

		btnSend.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				IsTesting = true;
				sendBTTestMsgHandler.post(SendBTTestMsgThread);
			}
		});

		spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				parent.setVisibility(View.VISIBLE);
				selectedAddress = position;
			}

			@Override
			public void onNothingSelected(AdapterView<?> arg0) {
				// TODO Auto-generated method stub

			}
		});

		btnStartHud.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				IsTesting = false;
				hudThreadHandler.post(HudThread);
				btnStartHud.setEnabled(false);
			}
		});

		msgHandler = new Handler() {

			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				if (msg.what == 0) { // 結束發送測試訊息線程並重新連接藍芽以重置(避免發送訊息過多而當機)
					sendBTTestMsgHandler.removeCallbacks(SendBTTestMsgThread);
					Log.e(TAG, "removecallbacks!");
					// 關閉藍芽通道
					if (outStream != null) {
						try {
							outStream.flush();
							outStream = null;
						} catch (IOException e) {
							Log.e(TAG,
									"ON PAUSE: Couldn't flush output stream.",
									e);
						}
					}
					try {
						if (BTIsConnected) {
							BTIsConnected = false;
							btSocket.close();
							btSocket = null;
						}
					} catch (IOException e2) {
						Log.e(TAG, "ON PAUSE: Unable to close socket.", e2);
					}
					// 重新連線藍芽
					sum = 0;
					Log.e(TAG, "Reconnecting...");
					connectBTHandler.post(ConnectBTThread);
				} else if (msg.what == 1) { // 開啟發送測試訊息線程
					Log.e(TAG, "post");
					sendBTTestMsgHandler.post(SendBTTestMsgThread);
				} else if (msg.what == 2) { // 重新連線藍芽
					Log.e(TAG, "Reconnecting...");
					connectBTHandler.post(ConnectBTThread);
				}

			}
		};

		// TODO: Bluetooth Initial
		if (D)
			Log.e(TAG, "+++ ON CREATE +++");

		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
			Toast.makeText(this, "Bluetooth is not available.",
					Toast.LENGTH_LONG).show();
			finish();
			return;
		}

		if (!mBluetoothAdapter.isEnabled()) {
			Toast.makeText(this,
					"Please enable your BT and re-run this program.",
					Toast.LENGTH_LONG).show();
			finish();
			return;
		}

		if (D)
			Log.e(TAG, "+++ DONE IN ON CREATE, GOT LOCAL BT ADAPTER +++");

		// 註冊一個BroadcastReceiver,等等會用來接收搜尋到裝置的消息
		IntentFilter filter = new IntentFilter();
		filter.addAction(BluetoothDevice.ACTION_FOUND);
		filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
		filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
		filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
		registerReceiver(mReceiver, filter);
		ReceiverIsRegisted = true;
	}

	private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			BluetoothDevice device = null;
			// 當收尋到裝置時
			if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
				Log.e(TAG, "Discovered");
				// 取得藍芽裝置這個物件
				device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				// 取得裝置名稱字串
				String newDeviceName = device.getName().toString();
				// 取得裝置MAC地址
				String newDeviceAddress = device.getAddress().toString();
				// 先比較添加的值是否已存在,不存在纔可添加入Spinner選單
				for (int i = 0; i < arrayAdapterDeviceName.getCount(); i++) {
					if (newDeviceName.equals(arrayAdapterDeviceName.getItem(i))) {
						return;
					}
				}
				if (!newDeviceName.equals("")) {
					arrayAdapterDeviceName.add(newDeviceName); // 將值添加到adapter
					allDevicesAddress.add(newDeviceAddress); // 將地址添加到ArrayList
					arrayAdapterDeviceName.notifyDataSetChanged();
				}
			} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent
					.getAction())) {
				device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				switch (device.getBondState()) {
					case BluetoothDevice.BOND_BONDING:
						Log.e(TAG, "Bluetooth device is pairing...");
						break;
					case BluetoothDevice.BOND_BONDED:
						Log.e(TAG, "Pairing complete");
						break;
					case BluetoothDevice.BOND_NONE:
						Log.e(TAG, "Cancel paring");
						break;
					default:
						break;
				}
			}
		}
	};

	Runnable ConnectBTThread = new Runnable() {

		@Override
		public void run() {
			// TODO Auto-generated method stub

			// Blocking connect, for a simple client nothing else can
			// happen until a successful connection is made, so we
			// don't care if it blocks.
			if (selectedAddress == 0) {
				return;
			}

			// 停止搜尋以減少資源消耗
			if (mBluetoothAdapter.isDiscovering()) {
				mBluetoothAdapter.cancelDiscovery();
				Log.e(TAG, "Cancel discovery");
			}

			// 連結到該裝置
			BluetoothDevice device = mBluetoothAdapter
					.getRemoteDevice(allDevicesAddress.get(selectedAddress)
							.toString());
			try {
				btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				Log.e(TAG, "Connect: Socket creation failed.", e);
			}
			try {
				btSocket.connect();
				BTIsConnected = true;
				Log.e(TAG,
						"ON RESUME: BT connection established, data transfer link open.");
			} catch (IOException e) {
				try {
					btSocket.close();
					BTIsConnected = false;
				} catch (IOException e2) {
					Log.e(TAG,
							"ON RESUME: Unable to close socket during connection failure",
							e2);
				}
			}
			Log.e(TAG, "BTisConnected = " + Boolean.toString(BTIsConnected));
			Message msg = msgHandler.obtainMessage();
			if (!BTIsConnected) {
				btnSend.setEnabled(false);
				btnStartHud.setEnabled(false);
				setTitle("Not connected");
				Log.e(TAG, "Try reconnect");
				msg.what = 2;
				msgHandler.sendMessage(msg); // 重新連線
			} else {
				btnSend.setEnabled(true);
				btnStartHud.setEnabled(true);
				setTitle("Connected");
				// Create a data stream so we can talk to server.
				if (D)
					Log.e(TAG, "+ ABOUT TO SAY SOMETHING TO SERVER +");
				if (IsTesting && hasFirstMatch) {
					msg.what = 1;
					msgHandler.sendMessage(msg); // 已重新連線,恢復發送測試訊息
				}
				hasFirstMatch = true;
			}
		}
	};

	Runnable SendBTMsgThread = new Runnable() {

		@Override
		public void run() {
			sum++;
			// Check if BT connection was established.
			if (!BTIsConnected) {
				return;
			}

			try {
				outStream = btSocket.getOutputStream();
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// Send msg start symbol
			String messageStart = "S";
			byte[] msgBufferStart = messageStart.getBytes();

			try {
				outStream.write(msgBufferStart);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// send msg
			String message = Integer.toString((int) (speed));
			// String message=et1.getText().toString();
			byte[] msgBuffer = message.getBytes();

			try {
				outStream.write(msgBuffer);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// Send msg end symbol
			String messageEnd = "E";
			byte[] msgBufferEnd = messageEnd.getBytes();

			try {
				outStream.write(msgBufferEnd);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}
			Message msg = msgHandler.obtainMessage();
			if ((android.os.Build.VERSION.SDK_INT < NEED_BT_RECONNECT_DEVICE_MAX_SDK)
					&& (sum > BT_RECONNECT_LIMIT_TIME)) {
				msg.what = 0;
				msgHandler.sendMessage(msg); // 重新連線以確保藍芽通道連線順暢,不重連可能會當機
			}

			Log.e(TAG, "sendThread: " + sum + " times.");
		}
	};

	Runnable SendBTTestMsgThread = new Runnable() {

		@Override
		public void run() {
			sum++;

			// Check if BT connection was established.
			if (!BTIsConnected) {
				return;
			}

			try {
				outStream = btSocket.getOutputStream();
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// Send msg start symbol
			String messageStart = "S";
			byte[] msgBufferStart = messageStart.getBytes();

			try {
				outStream.write(msgBufferStart);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// send msg
			String message = et1.getText().toString();
			byte[] msgBuffer = message.getBytes();

			try {
				outStream.write(msgBuffer);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}

			// Send msg end symbol
			String messageEnd = "E";
			byte[] msgBufferEnd = messageEnd.getBytes();

			try {
				outStream.write(msgBufferEnd);
			} catch (IOException e) {
				Log.e(TAG, "ON RESUME: Exception during write.", e);
				BTIsConnected = false;
				setTitle("Not connected");
			}
			// 以下為循環發送訊息測試,若只發送一次則不需要
			// Message msg = msgHandler.obtainMessage();
			//
			// if ((android.os.Build.VERSION.SDK_INT <
			// NEED_BT_RECONNECT_DEVICE_MAX_SDK)
			// && sum > BT_RECONNECT_LIMIT_TIME) {
			// msg.what = 0;
			// msgHandler.sendMessage(msg); // 重新連線以確保藍芽通道連線順暢,不重連可能會當機
			// } else {
			// msg.what = 1;
			// msgHandler.sendMessage(msg);
			// }
			// Log.e(TAG, "sendThread: " + sum + " times.");
		}
	};

	Runnable HudThread = new Runnable() {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			initControl();
		}
	};

	private void initControl() {
		// GPS啟動
		locationManager = (LocationManager) this.getSystemService(SERVERNAME);
		if (getGpsStates()) {

			getLocation();
		} else {
			Toast.makeText(this,
					"Please enable your GPS, and re-run this program. ",
					Toast.LENGTH_LONG).show();
			btnStartHud.setEnabled(true);
			hudThreadHandler.removeCallbacks(HudThread);
		}
	}

	// 打開GPS
	public boolean getGpsStates() {

		if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
			return true;
		} else
			return false;
	}

	private void getLocation() {
		try {
			Criteria criteria = new Criteria();
			criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度
			criteria.setAltitudeRequired(false);
			criteria.setBearingRequired(false);
			criteria.setCostAllowed(true);
			criteria.setPowerRequirement(Criteria.POWER_LOW); // 低功耗

			// String provider = locationManager.getBestProvider(criteria,
			// true); // 獲取GPS信息

			// 設置監聽器,自動更新的最小時間為間隔(N*1000)秒或最小位移變化超過N米
			locationManager.requestLocationUpdates(
					LocationManager.GPS_PROVIDER, 1 * 1000, (float) 0.5,
					locationListener);
			getSpeedThreadHandler.post(GetSpeedThread); // 改用GetSpeedThread來得到速度

			locationManager.addGpsStatusListener(gpsstatusListener);

		} catch (Exception e) {

		}
	}

	// 移動判定線程
	Runnable GetSpeedThread = new Runnable() {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			if (speed == lastSpeed) { //若和五秒前的速度一樣,則視為靜止,速度為零
				speed = 0;
				tvGPSInfo.setText("GPS Info" + "\n" + "  速度  = " + speed
						+ "km/h" + "\n Thread Sum = " + sum);
				if (BTIsConnected) {
					sendBTMsgHandler.post(SendBTMsgThread);
				} else {
					setTitle("Not connected");
				}
			} else {
				lastSpeed = speed;
			}
			getSpeedThreadHandler.postDelayed(GetSpeedThread, STATIC_CONTINUED_TIME);
		}

	};

	private final LocationListener locationListener = new LocationListener() {

		public void onLocationChanged(Location location) {
			speed = (float) (location.getSpeed() * 3.6);// 速度
			if (speed < 3.0) { // 速度修正,若時速在3km/h內則視為0km/h,因為要位移0.85m以上,時速才會高於3.06km/h,0.85m相當於沒有動,視為是定位的誤差
				speed = 0;
			}
			tvGPSInfo.setText("GPS Info" + "\n" + "  速度  = " + speed + "km/h"
					+ "\n Thread Sum = " + sum);
			if (BTIsConnected) {
				sendBTMsgHandler.post(SendBTMsgThread);
			} else {
				setTitle("Not connected");
			}

		}

		public void onProviderDisabled(String provider) {
			// WriteDB(null);
		}

		public void onProviderEnabled(String provider) {
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
			// "<locationListener>本地監聽狀態改變\n");
			switch (status) {

				case LocationProvider.AVAILABLE:
					// "<locationListener>有可用衛星\n");
					break;
				case LocationProvider.OUT_OF_SERVICE:
					// "<locationListener>無可用衛星服務\n");
					break;
				case LocationProvider.TEMPORARILY_UNAVAILABLE:
					// "<locationListener>暫時無可用衛星服務\n");
					break;
			}

		}

	};

	private final GpsStatus.Listener gpsstatusListener = new GpsStatus.Listener() {
		private GpsStatus gpsStatus;

		public void onGpsStatusChanged(int event) {

			gpsStatus = locationManager.getGpsStatus(null);

			switch (event) {
				case GpsStatus.GPS_EVENT_FIRST_FIX:
					// GPS時間
					gpdDate = new Date(gpsStatus.getTimeToFirstFix());
					break;

				case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
					// 得到所有收到的衛星的信息,包括 衛星的高度角、方位角、信噪比、和偽隨機號(衛星編號)
					Iterable<GpsSatellite> allSatellites;
					allSatellites = gpsStatus.getSatellites();

					tempBuilder.delete(0, tempBuilder.length());
					for (GpsSatellite gpsstate : allSatellites) {
						tempBuilder.append("方位角 " + gpsstate.getAzimuth());
						tempBuilder.append("\n");
						tempBuilder.append("高度角 " + gpsstate.getElevation());
						tempBuilder.append("\n");
						tempBuilder.append(" 偽隨機號(衛星編號)" + gpsstate.getPrn());
						tempBuilder.append("\n");
						tempBuilder.append(" 信噪比 " + gpsstate.getSnr());
						tempBuilder.append("\n");
					}

					int maxSatellites = gpsStatus.getMaxSatellites();

					Iterator<GpsSatellite> it = gpsStatus.getSatellites()
							.iterator();

					int satellite = 0;

					while (it.hasNext() && satellite <= maxSatellites) {

						// GpsSatellite s = it.next();
						it.next();
						satellite++;

					} // 計算衛星個數,可在此打印出衛星的其它信息

					satelliteCount = satellite;
					tempBuilder.append("當前衛星個數:" + satellite + "\n");
					tvGPSState.setText("GPS State" + "\n" + "  當前衛星個數:"
							+ satellite);
					break;

				case GpsStatus.GPS_EVENT_STARTED:
					// Event sent when the GPS system has started.

					// "<gpsstatusListener>開始信號GPS_EVENT_STARTED\n");
					break;

				case GpsStatus.GPS_EVENT_STOPPED:
					// Event sent when the GPS system has stopped.
					// "<gpsstatusListener>結束信號GPS_EVENT_STOPPED\n");
					break;

				default:
					// "<gpsstatusListener>沒有獲取到狀態信息\n");
					break;
			}
		}
	};

	@Override
	public void onStart() {
		super.onStart();
		if (D)
			Log.e(TAG, "++ ON START ++");
	}

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

		if (D) {
			Log.e(TAG, "+ ON RESUME +");
			Log.e(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");
		}
	}

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

		if (D)
			Log.e(TAG, "- ON PAUSE -");

	}

	@Override
	public void onStop() {
		super.onStop();
		if (D)
			Log.e(TAG, "-- ON STOP --");
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		if (D)
			Log.e(TAG, "--- ON DESTROY ---");

		if (outStream != null) {
			try {
				outStream.flush();
			} catch (IOException e) {
				Log.e(TAG, "ON PAUSE: Couldn't flush output stream.", e);
			}
		}

		if (mBluetoothAdapter.isDiscovering()) {
			mBluetoothAdapter.cancelDiscovery();
			Log.e(TAG, "Cancel discovery");
		}

		try {
			if (BTIsConnected) {
				btSocket.close();
				BTIsConnected = false;
			}

		} catch (IOException e2) {
			Log.e(TAG, "ON PAUSE: Unable to close socket.", e2);
		}

		if (ReceiverIsRegisted) {
			unregisterReceiver(mReceiver);
		}

		removeListener();
	}

	private void removeListener() {
		try {
			locationManager.removeUpdates(locationListener);
			locationManager.removeGpsStatusListener(gpsstatusListener);
		} catch (Exception e) {

		}
	}

	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		// TODO Auto-generated method stub
		return super.onMenuItemSelected(featureId, item);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// TODO Auto-generated method stub
		return super.onCreateOptionsMenu(menu);
	}

}



Android 行動裝置主介面Layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".HelmetHud" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/btnDiscovery"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Discovery" />

        <Button
            android:id="@+id/btnConnect"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Connect" />

    </LinearLayout>

    <Spinner
        android:id="@+id/spinner1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnStartHud"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:text="Start HUD" />

    <TextView
        android:id="@+id/tvGPSState"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GPS State"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/tvGPSInfo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GPS Info"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" >

        <EditText
            android:id="@+id/editText1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:ems="10" >

            <requestFocus />
        </EditText>

        <Button
            android:id="@+id/btnSend"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="SendTestMSG" />

    </LinearLayout>

</LinearLayout>



安全帽端設備Arduino主程序

#define CMD_MAXNUM 5
#define LED_DIGIT_NUM 8

int DIO_Pin = 8;  //pin 14 on the 75HC595(DIO)
int RCK_Pin = 9;  //pin 12 on the 75HC595(RCLK)
int SCK_Pin = 10; //pin 11 on the 75HC595(SCLK)

// 蜂鳴器變量
int melody = 523;
int duration = 500;  // 500 miliseconds

unsigned char LED_0F[] = 
{// 0	1	2	3	4	5	 6	  7	8	9
  0xC0,0xCF,0x92,0x86,0x8D,0xA4,0xA0,0xCE,0x80,0x84
}; //顛倒後的字元
// 全局變量
unsigned char SEG[LED_DIGIT_NUM];	//用於LED的4位顯示緩存
char cmd[CMD_MAXNUM]; //藍芽接收的字串(車速)

void SEG_OUT(unsigned char X)
{
	unsigned char i;
	for(i=LED_DIGIT_NUM;i>=1;i--)
	{
		if (X&0x80)
                    digitalWrite(DIO_Pin, HIGH);
                else 
                    digitalWrite(DIO_Pin, LOW);
		X<<=1;
		digitalWrite(SCK_Pin, LOW);
		digitalWrite(SCK_Pin, HIGH);
	}
}

void DisplaySegment(void)
{
	unsigned char led_table;          // 查表指針
	unsigned char i;
	//顯示第1位
        if(SEG[0]!=0)
        {
    	  led_table = LED_0F[SEG[0]];
    	  i = led_table;
          
    	  SEG_OUT(i);			
    	  SEG_OUT(0x01);		
          
    	  digitalWrite(RCK_Pin, LOW);
    	  digitalWrite(RCK_Pin, HIGH);
        }
	//顯示第2位
        if(SEG[0]!=0 || SEG[1]!=0)
        {
          led_table = LED_0F[SEG[1]];
	  i = led_table;
          
	  SEG_OUT(i);		
	  SEG_OUT(0x02);		
          
	  digitalWrite(RCK_Pin, LOW);
	  digitalWrite(RCK_Pin, HIGH);
        }
	
	//顯示第3位
        if(SEG[0]!=0 || SEG[1]!=0 || SEG[2]!=0)
        {
          led_table = LED_0F[SEG[2]];
      	  i = led_table;
          
      	  SEG_OUT(i);			
      	  SEG_OUT(0x04);	
          
      	  digitalWrite(RCK_Pin, LOW);
      	  digitalWrite(RCK_Pin, HIGH);
        }
	
	//顯示第4位
        
        led_table = LED_0F[SEG[3]];
	i = led_table;
        
    	SEG_OUT(i);			
    	SEG_OUT(0x08);		
        
    	digitalWrite(RCK_Pin, LOW);
    	digitalWrite(RCK_Pin, HIGH);
}

// 設定整個四合一型七段顯示器想要顯示的數字
// 參數number的範圍應是0~9999
void setNumber(int number)
{
  int n0, n1, n2, n3;
  n3 = number / 1000;
  number %= 1000;
  n2 = number / 100;
  number %= 100;
  n1 = number / 10;
  n0 = number % 10;
  
  // 每位數值清零
  SEG[0]=0;
  SEG[1]=0;
  SEG[2]=0;
  SEG[3]=0;
  // 求出每個位數的值後,分別更新
  // 因為要實現反射式HUD,所以位置要顛倒
  SEG[0]=n3; //第一位字元
  SEG[1]=n2; //第二位字元
  SEG[2]=n1; //第三位字元
  SEG[3]=n0; //第四位字元

  DisplaySegment();
}

void setup()
{
  Serial.begin(9600); // 藍牙模組預設baud rate = 9600
  pinMode(DIO_Pin, OUTPUT);
  pinMode(RCK_Pin, OUTPUT);
  pinMode(SCK_Pin, OUTPUT);
}

void loop()
{
  
  while (Serial.available()) //connected
  {
    memset(cmd, 0, CMD_MAXNUM);
    if(Serial.read() == 'S')
    {
      for(int i=0;i<CMD_MAXNUM;i++)
      {
        cmd[i] = Serial.read();
        if(cmd[i]=='E')
        {
          cmd[i]=0;
          break;
        }
        else if(cmd[i]<48 || cmd[i]>57)
        {
          cmd[i]=0;
          i--;
          continue;
        }
      }
      cmd[CMD_MAXNUM-1]='\0';
      Serial.print(cmd);
      Serial.print("\n");
    }
  }
  
  setNumber(atoi(cmd));
  if(atoi(cmd)>=70)
  {
    // 在 pin7 上輸出聲音,每個音階響 1 秒
    tone(7, melody, duration);

  }
} 





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章