首页 > 代码库 > 自制土砲智能安全帽

自制土砲智能安全帽

专案动机(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);

  }
}