天天看點

Android之藍牙驅動開發總結

一Bluetooth基本概念 3

二AndroidBluetooth架構 3

2.1Bluetooth架構圖 3

2.2Bluetooth代碼層次結構 3

三Bluetooth協定棧分析 4

3.1藍牙協定棧 4

3.2Android與藍牙協定棧的關系 6

四Bluetooth之HCI層分析 6

4.1HCI層與基帶的通信方式 6

4.2包的分析及研究 7

4.3通信過程的研究與分析 8

五Bluetooth之程式設計實作 8

5.1HCI層程式設計 8

5.2L2CAP層程式設計 11

5.3SDP層程式設計 13

六Bluetooth之啟動過程實作 14

6.1Bluetooth啟動步驟 14

6.2Bluetooth啟動流程 15

6.3Bluetooth資料流向 15

6.4Bluez控制流程 15

6.5Bluetooth啟動過程分析 16

七Bluetooth之驅動移植 17

7.1android系統配置 17

7.2啟動項修改 17

7.3電源管理rfkill驅動 17

7.4Rebuild Android image and reboot 18

7.5實作BT睡眠喚醒機制 18

7.6系統內建 19

八Bluetooth之調試與編譯 19

8.1Bluetooth驅動調試 19

8.2Bluetooth調試工具 20

九Bluetooth之應用程式開發 20

9.1Bluetooth的API開發 20

9.2The Basics開發 21

9.3Bluetooth Permissions開發 22

9.4Setting Up Bluetooth服務 22

9.5Finding Devices服務 23

9.6Connecting Devices服務 25

9.7Managing a Connection服務 31

9.8Working with Profiles服務 34

十總結與疑問 36

一Bluetooth基本概念

藍牙是無線資料和語音傳輸的開放式标準,它将各種通信裝置、計算機及其終端裝置、各種數字資料系統、甚至家用電器采用無線方式聯接起來。它的傳輸距離為10cm~10m,如果增加功率或是加上某些外設便可達到100m的傳輸距離。它采用2.4GHzISM頻段和調頻、跳頻技術,使用權向糾錯編碼、ARQ、TDD和基帶協定。TDMA每時隙為0.625μs,基帶符合速率為1Mb/s。藍牙支援64kb/s實時語音傳輸和資料傳輸,語音編碼為CVSD,發射功率分别為1mW、2.5mW和100mW,并使用全球統一的48比特的裝置識别碼。由于藍牙采用無線接口來代替有線電纜連接配接,具有很強的移植性,并且适用于多種場合,加上該技術功耗低、對人體危害小,而且應用簡單、容易實作,是以易于推廣。

藍牙技術的系統結構分為三大部分:底層硬體子產品、中間協定層和高層應用。底層硬體部分包括無線跳頻(RF)、基帶(BB)和鍊路管理(LM)。無線跳頻層通過2.4GHz無需授權的ISM頻段的微波,實作資料位流的過濾和傳輸,本層協定主要定義了藍牙收發器在此頻帶正常工作所需要滿足的條件。基帶負責跳頻以及藍牙資料和資訊幀的傳輸。鍊路管理負責連接配接、建立和拆除鍊路并進行安全控制。

二AndroidBluetooth架構

2.1 Bluetooth架構圖

Android藍牙系統分為四個層次,核心層、BlueZ庫、BlueTooth的适配庫、BlueTooth的JNI部分、Java架構層、應用層。下面先來分析Android的藍牙協定棧。

圖1面向庫的架構視圖

Linuxkernel層:

bluez協定棧、uart驅動,h4協定,hci,l2cap, sco, rfcomm

bluez層:

這是bluez使用者空間的庫,開源的bluetooth代碼,包括很多協定,生成libbluetooth.so。

library層:

libbluedroid.so等

framework層:

實作了Headset/Handsfree和A2DP/AVRCPprofile,但其實作方式不同Handset/Handfree是直接在bluez的RFCOMMSocket上開發的,沒有利用bluez的audioplugin,而A2DP/AVRCP是在bluez的audioplugin基礎上開發的,大大降低了實作的難度。

Android的藍牙協定棧采用BlueZ來實作,BlueZ分為兩部分:核心代碼和使用者态程式及工具集。

圖2面向程序的架構視圖

2.2 Bluetooth代碼層次結構

(1)JAVA層

frameworks/base/core/java/android/bluetooth/

包含了bluetooth的JAVA類。

(2)JNI層

frameworks/base/core/jni/android_bluetooth_開頭的檔案

定義了bluez通過JNI到上層的接口。

frameworks/base/core/jni/android_server_bluetoothservice.cpp

調用硬體适配層的接口system/bluetooth/bluedroid/bluetooth.c

(3)bluez庫

external/bluez/

這是bluez使用者空間的庫,開源的bluetooth代碼,包括很多協定,生成libbluetooth.so。

(4)硬體适配層

system/bluetooth/bluedroid/bluetooth.c

包含了對硬體操作的接口

system/bluetooth/data

publicvoid cancel() {

try{

mmServerSocket.close();

}catch (IOException e) { }

}

}

本例中,僅僅隻接受一個進來的連接配接,一旦連接配接被接受擷取到BluetoothSocket,就發送擷取到的BluetoothSocket給一個單獨的線程,然後關閉BluetoothServerSocket并跳出循環。

注意:accept()傳回BluetoothSocket後,socket已經連接配接了,是以在用戶端不應該呼叫connnect()。

manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸資料。

通常應該在處理完偵聽到的連接配接後立即關閉BluetoothServerSocket。在本例中,close()在得到BluetoothSocket後馬上被調用。還需要線上程中提供一個公共的方法來關閉私有的BluetoothSocket,停止服務端socket的偵聽。

(2)Connectingas a client

為了實作與遠端裝置的連接配接,你必須首先獲得一個代表遠端裝置BluetoothDevice對象。然後使用BluetoothDevice對象來擷取一個BluetoothSocket來實作來接。

下面是基本的步驟:

①用BluetoothDevice調用createRfcommSocketToServiceRecord(UUID)擷取一個BluetoothSocket對象。

這個初始化的BluetoothSocket會連接配接到BluetoothDevice。UUID必須比對伺服器裝置在打開BluetoothServerSocket時用到的UUID(用listenUsingRfcommWithServiceRecord(String,UUID))。可以簡單的生成一個UUID串然後在伺服器和用戶端都使用該UUID。

② 調用connect()完成連接配接

當調用這個方法的時候,系統會在遠端裝置上完成一個SDP查找來比對UUID。如果查找成功并且遠端裝置接受連接配接,就共享RFCOMM信道,connect()會傳回。這也是一個阻塞的調用,不管連接配接失敗還是逾時(12秒)都會抛出異常。

注意:要確定在調用connect()時沒有同時做裝置查找,如果在查找裝置,該連接配接嘗試會顯著的變慢,慢得類似失敗了。

執行個體:

下面是一個完成Bluetooth連接配接的樣例線程:

privateclass ConnectThread extends Thread {

privatefinal BluetoothSocket mmSocket;

privatefinal BluetoothDevice mmDevice;

publicConnectThread(BluetoothDevice device) {

//Use a temporary object that is later assigned to mmSocket,

//because mmSocket is final

BluetoothSockettmp = null;

mmDevice= device;

//Get a BluetoothSocket to connect with the given BluetoothDevice

try{

//MY_UUID is the app's UUID string, also used by the server code

tmp= device.createRfcommSocketToServiceRecord(MY_UUID);

}catch (IOException e) { }

mmSocket= tmp;

}

publicvoid run() {

//Cancel discovery because it will slow down the connection

mBluetoothAdapter.cancelDiscovery();

try{

//Connect the device through the socket. This will block

//until it succeeds or throws an exception

mmSocket.connect();

}catch (IOException connectException) {

//Unable to connect; close the socket and get out

try{

mmSocket.close();

}catch (IOException closeException) { }

return;

}

//Do work to manage the connection (in a separate thread)

manageConnectedSocket(mmSocket);

}

publicvoid cancel() {

try{

mmSocket.close();

}catch (IOException e) { }

}

}

注意到cancelDiscovery()在連接配接操作前被調用。在連接配接之前,不管搜尋有沒有進行,該調用都是安全的,不需要确認(當然如果有要确認的需求,可以調用isDiscovering())。

manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸資料。

在對BluetoothSocket的處理完成後,記得調用close()來關閉連接配接的socket和清理所有的内部資源。

9.7 Managing aConnection服務

如果已經連接配接了兩個裝置,他們都已經擁有各自的連接配接好的BluetoothSocket對象。那就是一個有趣的開始,因為你可以在裝置間共享資料了。使用BluetoothSocket,傳輸任何資料通常來說都很容易了:

(1)通過socket擷取輸入輸出流來處理傳輸(分别使用getInputStream()和getOutputStream())。

(2)用read(byte[])和write(byte[])來實作讀寫。

僅此而已。

當然,還是有很多細節需要考慮的。首要的,需要用一個專門的線程來實作流的讀寫。隻是很重要的,因為read(byte[])和write(byte[])都是阻塞的調用。read(byte[])會阻塞直到流中有資料可讀。write(byte[])通常不會阻塞,但是如果遠端裝置調用read(byte[])不夠快導緻中間緩沖區滿,它也可能阻塞。是以線程中的主循環應該用于讀取InputStream。線程中也應該有單獨的方法用來完成寫OutputStream。

示例:

下面是一個如上面描述那樣的例子:

privateclass ConnectedThread extends Thread {

privatefinal BluetoothSocket mmSocket;

privatefinal InputStream mmInStream;

privatefinal OutputStream mmOutStream;

publicConnectedThread(BluetoothSocket socket) {

mmSocket= socket;

InputStreamtmpIn = null;

OutputStreamtmpOut = null;

//Get the input and output streams, using temp objects because

//member streams are final

try{

tmpIn= socket.getInputStream();

tmpOut= socket.getOutputStream();

}catch (IOException e) { }

mmInStream= tmpIn;

mmOutStream= tmpOut;

}

publicvoid run() {

byte[]buffer = new byte[1024]; // buffer store for the stream

intbytes; // bytes returned from read()

//Keep listening to the InputStream until an exception occurs

while(true) {

try{

//Read from the InputStream

bytes= mmInStream.read(buffer);

//Send the obtained bytes to the UI Activity

mHandler.obtainMessage(MESSAGE_READ,bytes, -1, buffer)

.sendToTarget();

}catch (IOException e) {

break;

}

}

}

publicvoid write(byte[] bytes) {

try{

mmOutStream.write(bytes);

}catch (IOException e) { }

}

publicvoid cancel() {

try{

mmSocket.close();

}catch (IOException e) { }

}

}

構造函數中得到需要的流,一旦執行,線程會等待從InputStream來的資料。當read(byte[])傳回從流中讀到的位元組後,資料通過父類的成員Handler被送到主Activity,然後繼續等待讀取流中的資料。

向外發送資料隻需簡單的調用線程的write()方法。

線程的cancel()方法時很重要的,以便連接配接可以在任何時候通過關閉BluetoothSocket來終止。它應該總在處理完Bluetooth連接配接後被調用。

9.8 Working withProfiles服務

從Android3.0開始,BluetoothAPI就包含了對Bluetoothprofiles的支援。Bluetoothprofile是基于藍牙的裝置之間通信的無線接口規範。例如Hands-Freeprofile(免提模式)。如果行動電話要連接配接一個無線耳機,他們都要支援Hands-Freeprofile。

你在你的類裡可以完成BluetoothProfile接口來支援某一Bluetoothprofiles。AndroidBluetooth API完成了下面的Bluetoothprofile:

Headset:Headsetprofile提供了行動電話上的Bluetooth耳機支援。Android提供了BluetoothHeadset類,它是一個協定,用來通過IPC(interprocesscommunication)控制BluetoothHeadset Service。BluetoothHeadset既包含BluetoothHeadset profile也包含Hands-Freeprofile,還包括對AT指令的支援。

A2DP:AdvancedAudio Distribution Profile (A2DP)profile,進階音頻傳輸模式。Android提供了BluetoothA2dp類,這是一個通過IPC來控制BluetoothA2DP的協定。

下面是使用profile的基本步驟:

① 擷取預設的Bluetooth擴充卡。

②使用getProfileProxy()來建立一個與profile相關的profile協定對象的連接配接。在下面的例子中,profile協定對象是BluetoothHeadset的一個執行個體。

③設定BluetoothProfile.ServiceListener。該listener通知BluetoothProfileIPC用戶端,當用戶端連接配接或斷連伺服器的時候。

④在onServiceConnected()内,得到一個profile協定對象的句柄。

⑤一旦擁有了profile協定對象,就可以用它來監控連接配接的狀态,完成于該profile相關的其他操作。

例如,下面的代碼片段顯示如何連接配接到一個BluetoothHeadset協定對象,用來控制Headsetprofile:

BluetoothHeadsetmBluetoothHeadset;

//Get the default adapter

BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

//Establish connection to the proxy.

mBluetoothAdapter.getProfileProxy(context,mProfileListener, BluetoothProfile.HEADSET);

privateBluetoothProfile.ServiceListener mProfileListener = newBluetoothProfile.ServiceListener() {

publicvoid onServiceConnected(int profile, BluetoothProfile proxy) {

if(profile == BluetoothProfile.HEADSET) {

mBluetoothHeadset= (BluetoothHeadset) proxy;

}

}

publicvoid onServiceDisconnected(int profile) {

if(profile == BluetoothProfile.HEADSET) {

mBluetoothHeadset= null;

}

}

};

//... call functions on mBluetoothHeadset

//Close proxy connection after use.

mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

十 總結與疑問

1.藍牙驅動的上下電是怎麼完成的?是通過操作寄存器,還是其他的方式?

2.bccmd指令主要用來初始化藍牙,比如說上電,設定波特率,下載下傳firmware,它是如何實作的?

3.如何設定PSKEY鍵值?

4.藍牙協定棧bluez是如何通過dbus總線與framework層進行資料互動的?

文章轉載:http://www.cnblogs.com/MMLoveMeMM/articles/4025508.html