1. 程式人生 > >bluetooth模組學習總結

bluetooth模組學習總結

Android系統中的bluetooth模組

1. 藍芽是什麼

藍芽是一種低功耗的無線連線技術,是一種裝置間短距離的無線通訊方式,這句話表明藍芽以下幾個特性:

  1. 藍芽是一種無線通訊方式,即表示該通訊需要有對應的協議支援(藍芽無線通訊協議標準) 。
  2. 藍芽跨裝置使用。
  3. 低耗能技術。
  4. 藍芽屬於短距離通訊方式。

2. 藍芽有什麼

藍芽技術有兩種型別:

  1. Basic Rate/Enhanced Data Rate (BR/EDR)基本速率/增強資料速率即所謂的傳統藍芽技術(藍芽版本2.0/2.1):僅支援P2P一種通訊方式,即1:1裝置間通訊,具有持續無線連線、優化音訊流的特點,所以是藍芽耳機、藍芽揚聲器等音訊傳輸的理想方案
  2. Low Energy (LE)低功耗即所謂的新型的低功耗藍芽技術(藍芽版本4.0/4.1/4.2/4.3)
    所以藍芽模組可以分為經典藍芽模組(v1.1/1.2/2.0/2.1/3.0),低功耗藍芽模組(v4.0/4.1/4.2),以及藍芽雙模模組(支援藍芽所有版本,相容低功耗藍芽及經典藍芽)。

藍芽技術4種通訊方式:

  1. P2P通訊方式(舊版本):1:1裝置間通訊,具有持續無線連線、優化音訊流的特點,所以是藍芽耳機、藍芽揚聲器等音訊傳輸的理想方案
  2. P2P(point-to-point)(點對點):1:1支援短時間無限連線,優化了資料傳輸能量消耗,可用於無線鍵盤、無線滑鼠等
  3. broadcast(廣播資訊):1:m。可以實現本地化資訊共享。廣播資訊顧名思義,一裝置廣播資訊,其他對該資訊感興趣的裝置接受該資訊並進行處理。比如beacon
  4. mesh(網格):m:m

藍芽常用協議:

. 含義 作用 舉例
OppProfile Object Push Profie 檔案傳輸協議:用於藍芽裝置間的檔案傳輸 手機間的檔案傳輸
PbapServerProfile Phone Book Access Profile(PSE) 讀取聯絡人協議:作為server,本裝置的聯絡人可共享給其他裝置 提供聯絡人列表
PbapClientProfile Phone Book Access Profile(PCE) 讀取聯絡人協議:作為client角色,本裝置可讀取server端的聯絡人 讀取聯絡人列表
A2dpProfile Advanced Audio Distribution Profile(SRC:Source) 高階音訊分發協議:作為server提供音訊源 例如可以提供音訊源的手機
A2dpSinkProfile Advanced Audio Distribution Profile(SINK) 高階音訊分發協議:作為client播放接收到的音訊 車載藍芽,藍芽音響
HeadsetProfile Headset Profile 耳機協議:提供手機音訊 連線藍芽耳機
HfpClientProfile Hands-Free Profile 擴音裝置:播放音訊 藍芽耳機
HidProfile Human Interface Device 人機介面裝置 藍芽滑鼠,藍芽鍵盤
MapProfile Message Access Profile 讀取短訊息協議
SapProfile SIM Access Profile 讀取sim卡協議

3. 藍芽需要改什麼

藍芽與Android關係:

  1. Google推出的各Android系統:所支援的藍芽協議profile均是開啟狀態
  2. 晶片提供商(常見的諸如高通、mtk)修改後的Android原始碼–開發中稱之為base程式碼:新增或者修改某些藍芽profile
  3. 開發商拿到base程式碼進行進一步加工:新增或者修改某些profile

藍芽堆疊的常規結構:
這裡寫圖片描述
* 應用框架:
處於應用框架級別的是應用程式碼,它利用 android.bluetooth API 與藍芽硬體進行互動。此程式碼在內部通過 Binder IPC 機制呼叫藍芽程序。

  • 藍芽系統服務
    藍芽系統服務(位於 packages/apps/Bluetooth 中)被打包為 Android 應用,並在 Android 框架層實現藍芽服務和配置檔案。該應用通過 JNI 呼叫 HAL 層。
  • JNI
    與 android.bluetooth 相關聯的 JNI 程式碼位於 packages/apps/Bluetooth/jni 中。當發生特定藍芽操作時(例如發現裝置時),JNI 程式碼會呼叫 HAL 層並從 HAL 接收回調。
  • HAL
    硬體抽象層定義了 android.bluetooth API 和藍芽程序會呼叫的標準介面,並且您必須實現該接口才能使藍芽硬體正常工作。藍芽 HAL 的標頭檔案是 hardware/libhardware/include/hardware/bluetooth.h。另外,請檢視所有 hardware/libhardware/include/hardware/bt_*.h 檔案。
  • 藍芽堆疊
    系統為您提供了預設藍芽堆疊(位於 system/bt 中)。該堆疊會實現常規藍芽 HAL,並通過擴充套件程式和更改配置對其進行自定義。
  • 供應商擴充套件程式
    要新增自定義擴充套件程式和用於跟蹤的 HCI 層,一般可以建立一個 libbt-vendor 模組並指定這些元件。

藍芽核心架構:
這裡寫圖片描述

藍芽程式碼的實現主要包括3個方面:

  1. 介面UI
    1. 設定應用中藍芽的ui
    2. 藍芽本身這個系統應用中的ui
  2. 藍芽開關預設值
  3. 協議配置開關:手機是否要支援各種協議

藍芽程式碼分佈:

  1. 系統應用設定Settings中的藍芽相關,包括藍芽開關,藍芽掃描,藍芽配對框,藍芽重新命名框,藍芽選擇框等等。
    這裡寫圖片描述

  2. 系統中有個藍芽應用Bluetooth,包含藍芽檔案傳入傳出歷史記錄,藍芽配對框,藍芽檔案傳輸框等等。
    這裡寫圖片描述
    這裡寫圖片描述

  3. 藍芽協議的具體實現
    這裡寫圖片描述

  4. 整合的一些藍芽介面
    這裡寫圖片描述

4. 對藍芽功能新增/修改

開發一個測試工具,主要對bluetooth的Channel進行測試

  1. 通過暗碼進行開啟這個測試工具
  2. 可以測試不通的Channel

關於這個工具的開發其實系統已經提供了相應的介面,介面的實現都是不用管的,只要將介面層層包裝,並在工具apk中呼叫包裝好的介面即可。

4.1 首先,看看系統什麼地方定義了該介面:

在檔案hardware/libhardware/include/hardware/bluetooth.h中有個如下結構體:

/** Represents the standard Bluetooth DM interface. */
typedef struct {
    /** set to sizeof(bt_interface_t) */
    size_t size;
    /**
     * Opens the interface and provides the callback routines
     * to the implemenation of this interface.
     */
    int (*init)(bt_callbacks_t* callbacks );

    /** Enable Bluetooth. */
    int (*enable)(bool guest_mode);

    /** Disable Bluetooth. */
    int (*disable)(void);

  /*此處省略*/
    /** Get Bluetooth profile interface */
    const void* (*get_profile_interface) (const char *profile_id);

    /** Bluetooth Test Mode APIs - Bluetooth must be enabled for these APIs */
    /* Configure DUT Mode - Use this mode to enter/exit DUT mode */
    int (*dut_mode_configure)(uint8_t enable);

    /* Send any test HCI (vendor-specific) command to the controller. Must be in DUT Mode */
    int (*dut_mode_send)(uint16_t opcode, uint8_t *buf, uint8_t len);
    /** BLE Test Mode APIs */
    /* opcode MUST be one of: LE_Receiver_Test, LE_Transmitter_Test, LE_Test_End */
    int (*le_test_mode)(uint16_t opcode, uint8_t *buf, uint8_t len);
/*  該結構體中的函式指標dut_mode_configure && le_test_mode就是提供給上層使用的負責藍芽的開關及基本控制標準介面,本次開發主要就使用這2個介面。*/
} bt_interface_t;

4.2 其次,問題就轉換為如何呼叫這個2介面了。 在 apk中呼叫c/c++層的介面需要通過JNI層將這2介面個進行包裝

  1. 在檔案packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp檔案中新增對應方法
#define HCI_LE_TRANSMITTER_TEST_OPCODE 0x201E
#define HCI_LE_END_TEST_OPCODE 0x201F
static jint setTestModeNative(JNIEnv *env, jobject object, jint mode) {
    ALOGD("%s setTestModeNative mode = %d",__FUNCTION__, mode);
    #if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
        ALOGI("%s mode = %d",__FUNCTION__, mode);
        if (!sBluetoothInterface) return -1;
        return sBluetoothInterface->dut_mode_configure(mode);/*呼叫藍芽hal層的介面*/
    #endif
        return -1;
}
static jint setBtChannelNative(JNIEnv *env, jobject object, jint position) {
    ALOGD("%s setBtChannelNative position = %d",__FUNCTION__, position);
    #if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
        ALOGI("%s position = %d",__FUNCTION__, position);
        if (!sBluetoothInterface) return -1;
        unsigned char buf[3];
        memset(buf, 0, sizeof(buf));
        if (position < 0) {
             return sBluetoothInterface->le_test_mode(HCI_LE_END_TEST_OPCODE, buf, 0);
        }
        buf[0] = position; /* tx_channel 0-39*/
        buf[1] = 0x25; /* length of test data 0-37*/
        buf[2] = 0; /* packet payload <9*/
        return sBluetoothInterface->le_test_mode(HCI_LE_TRANSMITTER_TEST_OPCODE, buf, 3);
    #endif
        return -1;
}
static JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    {"setSocketOptNative", "(III[BI)I", (void*) setSocketOptNative}
     /*在陣列sMethods中註冊新加的2個方法為native方法供java層使用*/
    ,{"setTestModeNative", "(I)I", (void *)setTestModeNative},
    {"setBtChannelNative","(I)I",(void *)setBtChannelNative}
    /**/
};
  1. 將新增的2個方法包裝到AdapterService.java服務中,執行不同程序的應用呼叫。

    a. 在檔案frameworks/base/core/java/android/bluetooth/IBluetooth.aidl中新增新方法的呼叫介面,具體如下:

interface IBluetooth
{
/*此處省略*/
 int setBtTestMode(int mode);
 int setBtChannel(int position);
/*此處省略*/
}

在packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java中實現IBluetooth中的2個介面,這樣對於任何持有AdapterService控制代碼的物件都可以使用新加的2個方法。

import android.bluetooth.IBluetooth;/*匯入aidl介面,實現方法跨程序呼叫*/
public class AdapterService extends Service {
/*此處省略*/
    static {
        System.loadLibrary("bluetooth_jni");/*load jni靜態庫*/
        classInitNative();/*呼叫native中的init方法,為結構體bt_interface_t賦值*/
    }
    private native int setTestModeNative(int mode);
    private native int setBtChannelNative(int position);
    /**
     * Handlers for incoming service callsH
     */
    private AdapterServiceBinder mBinder;
    /**
     * The Binder implementation must be declared to be a static class, with
     * the AdapterService instance passed in the constructor. Furthermore,
     * when the AdapterService shuts down, the reference to the AdapterService
     * must be explicitly removed.
     *
     * Otherwise, a memory leak can occur from repeated starting/stopping the
     * service...Please refer to android.os.Binder for further details on
     * why an inner instance class should be avoided.
     *
     */
    private static class AdapterServiceBinder extends IBluetooth.Stub {
/*此處省略*/
         public int setBtTestMode(int mode) {
             Log.d(TAG, "setBtTestMode mode " + mode);
             AdapterService service = getService();
             if (service == null) return -1;
             return service.setTestModeNative(mode);
         }
         public int setBtChannel(int position) {
             Log.d(TAG, "setBtChannel position " + position);
             AdapterService service = getService();
             if (service == null) return -1;
             return service.setBtChannelNative(position);
         }
    }
/*此處省略*/
}
  1. 到目前為止,新增的2個方法還處在packages/apps/Bluetooth模組中,繼續將新加的方法包裝到BluetoothAdapter.java中,方便應用程式呼叫,具體修改如下。
public final class BluetoothAdapter {
/*此處省略*/
    public int setBtTestMode(int mode) {
        Log.d(TAG, "setBtTestMode mode : " + mode);
        //IBluetooth service = mBluetoothAdapter.mService;
        if (mService == null) {
            return -1;
        }
        try {
            return mService.setBtTestMode(mode);
        } catch (Exception ex) {
            Log.w(TAG, "Unhandled exception: " + ex);
        }
        return -1;
    }

    public int setBtChannel(int position) {
        Log.d(TAG, "setBtChannel position : " + position);
        //IBluetooth service = mBluetoothAdapter.mService;
        if (mService == null) {
            return -1;
        }
        try {
            return mService.setBtChannel(position);
        } catch (Exception ex) {
            Log.w(TAG, "Unhandled exception: " + ex);
        }
        return -1;
    }
   /*此處省略*/
}

4.3 最後,寫個測試apk,呼叫封裝好的方法。

package com.android.BluetoothTestMode;
import com.android.BluetoothTestMode.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Toast;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Spinner;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;

public class BluetoothTestMode extends Activity implements AdapterView.OnItemSelectedListener {
    private Context mContext;
    private BluetoothAdapter mBtAdapter;

    private final String TAG = "BluetoothTestMode";

    private static final int ENABLE_BT_TEST_MODE_DELAY = 3;

    private Button mButton01 = null;
    private Button mButton02 = null;
    private Spinner mSpinner = null;
    private int mBluetoothChannel = 0;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mContext = this;

        // Get the local Bluetooth adapter
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();

        setContentView(R.layout.main);

        mButton01 = (Button) findViewById(R.id.Button01);
        mButton02 = (Button) findViewById(R.id.Button02);
//bluetooth test mode channel setting
        mSpinner = (Spinner) findViewById(R.id.Bluetooth_Channel_Spinner);
        mSpinner.setSelection(0, true);
        mSpinner.setOnItemSelectedListener(this);
        mButton01.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                mButton01.setEnabled(false);
                mButton02.setEnabled(true);
                if (!mBtAdapter.isEnabled()) {
                    mBtAdapter.enable();
                    synchronized(mHandler) {
                        if(mHandler.hasMessages(ENABLE_BT_TEST_MODE_DELAY)){
                            mHandler.removeMessages(ENABLE_BT_TEST_MODE_DELAY);
                        }
                        mHandler.sendEmptyMessageDelayed(ENABLE_BT_TEST_MODE_DELAY, 5000);
                    }
                    Toast.makeText(BluetoothTestMode.this, "BT is turning on, please wait...", Toast.LENGTH_SHORT).show();
                }else {
                    int ret = mBtAdapter.setBtTestMode(1);
                    if (ret == -1) {
                        mButton01.setEnabled(true);
                        mButton02.setEnabled(false);
                        Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
                        }
                    Log.d(TAG, "BT already ON! enableBtTestMode " + ret);
                }
            }
        });

        mButton02.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
                mButton01.setEnabled(true);
                mButton02.setEnabled(false);
                if (mBtAdapter.isEnabled()) {
                    mBtAdapter.setBtChannel(-1);
                    mBtAdapter.setBtTestMode(0);
                    mBtAdapter.disable();
                    Toast.makeText(BluetoothTestMode.this, "BT disabled...", Toast.LENGTH_SHORT).show();
                }else {
                    Toast.makeText(BluetoothTestMode.this, "BT already OFF!", Toast.LENGTH_SHORT).show();
                }
            }
        });

    }

    /**
     * Called when the activity will start interacting with the user.
     */
    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();

    }

    /**
     * Called when the system is about to start resuming a previous activity.
     */
    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();

    }

    /**
     * The final call you receive before your activity is destroyed.
     */
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        if (mBtAdapter.isEnabled()) {
            mBtAdapter.setBtChannel(-1);
            mBtAdapter.setBtTestMode(0);
            mBtAdapter.disable();
         }
    }
//bluetooth test mode channel setting
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (parent == mSpinner) {
            mBluetoothChannel = position;
            if (!mBtAdapter.isEnabled()) {
                mBtAdapter.enable();
             }
            int ret = mBtAdapter.setBtChannel(-1);
            Log.d(TAG,"setBtChannel end test " + ret);
            ret = mBtAdapter.setBtChannel(mBluetoothChannel);
            Log.d(TAG,"setBtChannel start test " + ret);
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        //
    }
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == ENABLE_BT_TEST_MODE_DELAY){
                int ret = mBtAdapter.setBtTestMode(1);
                if (ret == -1) {
                    mButton01.setEnabled(true);
                    mButton02.setEnabled(false);
                    Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
                }
                Log.d(TAG, "enableBtTestMode " + ret);
            }
        }
    };
}