Android蓝牙系统(精华篇)

第1章 Android蓝牙系统

1.1 蓝牙技术简介

蓝牙(Bleuetooth)原是十世纪统一了丹麦的一个国王的名字,现取其“统一”的含义,用来意在统一无线局域网通讯的标准的蓝牙技术。蓝牙技术是爱立信IBM,Intel等世界5家著名大公司在1998年联合推出的一项无线通讯规范。随后成立的蓝牙技术特殊兴趣组织(SIG)来负责该技术的开发和技术协议的制定,如今全世界已有1800多家公司加盟该组织,最近微软公司也正式加盟并成为SIG组织的领导成员之一。它以低成本的近距离无线连接为基础,为移动通信设备建立一个短程无线连接。其实质内容是建立通用的无线电空中接口,使计算机和通信设备进一步结合,让不同的厂家生产便携式设备在没有电缆或电线相互连接的情况下,能在近距离范围内具有相互通信的一种技术。

1.2 蓝牙技术的特点

  • 全球范围适用

蓝牙技术使用无需经过许可的工业、科研和医疗(ISM)波段(2.4至2.485 GHz),使用展频、调频、全双工信号,标称速率为1600跳/秒。在大多数国家,无需经过许可便可使用2.4 GHz ISM波段。

  • 抗干扰

蓝牙技术的适配跳频(AFH)能力的设计目的是为了减少共用2.4 GHz频谱的无线技术之间出现的干扰。该功能会在频谱中寻找并无被占用的频带以供蓝牙技术使用。AFH的工作原理是识别该频谱中的其他设备并避开这些设备所用的频带。跳频功能以1 MHz的频率在79个频段中进行切换,从而获得了较高的抗干扰能力,同时使该频谱中能够实现更加高效的传输。有了跳频功能,尽管其他技术与蓝牙技术同时使用,但蓝牙技术的用户仍能享有优质的性能表现。

  • 射程

射程根据不同的具体应用而定,尽管核心规格规定了最低射程,但这并非限制,制造商仍可根据其具体用例调整射程应用。

根据具体应用中使用的射频种类,射程将有所不同:

第三类射频 – 射程最高1米或3英尺

第二类射频 – 最常见于移动设备,射程为10米或33英尺

第一类射频 – 主要用于工业用例,射程为100米或300英尺

  • 低功耗

最常用的射频为第二类,其能耗为2.5 mW。蓝牙技术的设计能耗非常之低。此外,规格允许射频处于非活跃状态时可以断电则进一步降低了能耗。3.0版HS中的通用替代MAC/PHY能够发现高速设备的AMP,并仅在需要进行数据传输时开启射频,实现了节能优势,同时增强了射频的安全性。对于无需高速数据传输率但需要最大限度延长电池寿命的设备而言,蓝牙低耗能技术为其实现了优化效果,其耗电量仅为传统蓝牙技术的1/2至1/100。

1.3 蓝牙技术在android中的应用

1.3.1 蓝牙服务的启动

在前面章节android启动过程中介绍到android服务的启动,init进程中,启动Zygote后,然后由SystemServer启动一系列服务,蓝牙服务就是在这个时候启动的。详细见代码:

/framework/base/services/java/com/android/server/SystemServer.java

if (SystemProperties.get("ro.kernel.qemu").equals("1")) {

} else {

……

bluetooth = new BluetoothService(context);

……

bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);

……

if (airplaneModeOn == 0 && bluetoothOn != 0) {

bluetooth.enable();

}

Bluetooth服务的代码首先通过SystemProperties的get方法来判断系统是不是使用模拟器内核,如果是使用模拟器内核来启动android的系统,那么就会跳过蓝牙服务的启动,也就是说Android 4.0模拟器是不支持蓝牙系统的。否则就是一个实在的设备产品(ro.kernel.qemu=0)

就是构造一个bluetooth的服务(BluetoothService)和一个蓝牙耳机服务(BluetoothA2dpService)。

代码段最后一部分是判断开机是否要启用蓝牙,通过函数我们可以看到如果设备的飞行模式是关闭的并且bluetooth的那个开关是在on。就是调用bluetoothService的enable方法使得我们设备开机的时候就将蓝牙开启。飞行模式就是那些使用无线频谱的模块都必须关掉,譬如:wifi,Bluetooth,GPS等。接下来就是BluetoothService的enable方法了。

/framework/base/core/java/android/server/BluetoothService.java

public synchronized boolean enable(boolean saveSetting) {

mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,"Need BLUETOOTH_ADMIN permission");

if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {

return false;

}

mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting);

return true;

}

蓝牙服务的enable的方法会先判断进程有没有操作权限,需要蓝牙管理的权限才能去enable蓝牙模块,然后还会再次判断系统的飞行模式有没有打开,如果此时飞行模式是on的话,那么会返回,还是不能打开服务。在确保拥有权限并且不是出于飞行模式的情况下,就会往蓝牙状态机发送一个USER_TURN_ON的命令。下面介绍一下android中的蓝牙状态机。

  • Poweroff

这就是蓝牙模块没有初始化的状态,这时候硬件模块是出于没有上电的状态。

  • Warmup

这个状态就是给设备上电,使设备能够从没电到待机状态。

  • Hotoff

Hotoff我个人理解就是在模块上电了,出于一种待命的状态,如果收到了turn_on_coninue的命令时候就会去将蓝牙模块切换到工作状态。如果接收到了turn_cold的命令时候,设备就会断电进入poweroff状态。

  • Switching

这也是一个中间状态,需要继续接收命令。

  • Bluetoothon

这时蓝牙模块出于正常工作的状态。

根据android中蓝牙状态的源码中,具体的各个状态机相互转换图如下:

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private class PowerOff extends State {

public void enter() {

if (DBG) log("Enter PowerOff: " + getCurrentMessage().what);

}

……

case USER_TURN_ON:

broadcastState(BluetoothAdapter.STATE_TURNING_ON);

transitionTo(mWarmUp);

……

if (prepareBluetooth()) {

if ((Boolean) message.obj) {

persistSwitchSetting(true);

}

deferMessage(obtainMessage(TURN_ON_CONTINUE));

蓝牙状态机初始化时PowerOff的,从上面的BluetoothService的enable函数中USER_TURN_ON命令。从上面代码中可以看出蓝牙状态机在接收到USER_TURN_ON后,首先就像蓝牙适配器广播蓝牙正处于STATE_TRUNING_ON的状态,蓝牙的适配器的蓝牙状态有四个:

分别是,state_off(10),state_turning_on(11),state_on(12),state_turning_off(14)。由于我们刚开机所以蓝牙适配器的状态必然是从10->11。然后将蓝牙状态机的状态切换到mWaremUp状态。

接下来调用了prepareBluetooth()方法。接下来看看prepareBluetooth方法。代码如下:

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private boolean prepareBluetooth() {

if (mBluetoothService.enableNative() != 0) {

return false;

}

……

int retryCount = 2;

boolean eventLoopStarted = false;

while ((retryCount-- > 0) && !eventLoopStarted) {

mEventLoop.start();

while ((pollCount-- > 0) && !eventLoopStarted) {

if (mEventLoop.isEventLoopRunning()) {

eventLoopStarted = true;

break;

}

在preprareBluetooth方法中,首先就是调用了BluetoothService的enableNative()的方法,只要一看到这种带Native的方法,JNI的代码是少不了的。由于enableNative方法走的路有点多,所以先直接到BluetoothService的代码中寻找enableNative()看个究竟。

framework/base/core/java/android/server/BluetoothService.java

/*package*/ native int enableNative();

Framework/base/core/jni/ android_server_BluetoothService.cpp

static JNINativeMethod sMethods[] = {

……

{"enableNative", "()I", (void *)enableNative},

……

}

static jint enableNative(JNIEnv *env, jobject object) {

return bt_enable();

}

从上面的代码可以看出,BluetoothService的enableNative就是直接调用了JNI的代码,JNI是java native interface的 缩写,中文叫java本地接口。Android上层跑的java代码,而底层代码都是c语言。以android的一贯作风是通过JNI代码调用HAL层,然后就可以直接调用驱动代码或者经由内核达到操作驱动代码。enableNative的代码很简单,就是调用了bt_enable。我们可以继续找到这个函数的实现。

System/bluetooth/bluedroid/bluetooth.c

int bt_enable() {

……

if (set_bluetooth_power(1) < 0) goto out

……

if (property_set("ctl.start", "hciattach") < 0)

……

for (attempt = 1000; attempt > 0; attempt--) {

hci_sock = create_hci_sock();

……

ret = ioctl(hci_sock, HCIDEVUP, HCI_DEV_ID);

……

}

Set_bluetooth_power()函数会根据蓝牙的硬件开关,也就是hci设备注册的时候会同时在linux内核中注册一个rfkill类,比如我们在电脑键盘上面可能会看见一个按键来开关蓝牙或者wifi之类的。这里会去读这个键值,如果是1代表可以开启蓝牙的,否则是没法使用蓝牙的,在开发过程中如果没这样的按键,可将这行代码拿掉。Propery_set(“ctl.start”,hciattach)。这个函数会去启动hciattach服务,具体这个服务是以二进制文件存储在系统system/bin目录下面的。

我们可以从andriod启动脚本文件找到名字叫hciattach服务。当然这个是针对接串口的蓝牙来说需要启动服务,如果我们的设备是通过USB总线接入系统的话,其实这个服务也是可以不启动的。剩下的代码是一个for循环,先建立一个bluetooth的套接字,然后通过ioctl来和bluez的代码来打开蓝牙设备,可以重试1000次。接下来的代码就要跑到内核的BlueZ了。

Kernel/net/bluetooth/hci_sock.c

static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg){

……

case HCIDEVUP:

if (!capable(CAP_NET_ADMIN))

return -EACCES;

return hci_dev_open(arg);

……

}

从上面的ioctl下来的代码可以看出,当函数第二个参数cmd为HCIDEVUP时,就会直接调用hci_dev_open(arg)方法。这个函数就好比我们在终端下面使用蓝牙调试工具hciconfig,执行了

#hciconfig hci0 up。

Kernel/net/bluetooth/hci_core.c

int hci_dev_open(__u16 dev)

{

……

hdev = hci_dev_get(dev);

……

if (hdev->open(hdev)) {

ret = -EIO;

goto done;

}

}

在kernel的bluez调用hci_dev_open,而在这个函数中又hdev->open(hdev),这个就是我们驱动注册时候的回调函数open。由于我们平台使用的是usb的蓝牙接入方式,我就以usb的蓝牙驱动为例,看看驱动的open函数。

Kernel/driver/bluetooth/btusb.c

static int btusb_open(struct hci_dev *hdev){

……

err = btusb_submit_intr_urb(hdev, GFP_KERNEL);

……

err = btusb_submit_bulk_urb(hdev, GFP_KERNEL);

……

}

USB hci设备打开后,首先将设备的interface配置为HCI_RUNNING状态,然后为数据传输初始化设备的端点和管道,初始化和填充urb。代码到这,蓝牙设备就算是真正打开了。

回到之前的蓝牙状态机的代码:

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private boolean prepareBluetooth() {

if (mBluetoothService.enableNative() != 0) {

return false;

}

……

int retryCount = 2;

boolean eventLoopStarted = false;

while ((retryCount-- > 0) && !eventLoopStarted) {

mEventLoop.start();

while ((pollCount-- > 0) && !eventLoopStarted) {

if (mEventLoop.isEventLoopRunning()) {

eventLoopStarted = true;

break;

}

mBluetoothService在enableNative()函数主要功能就是通过一系列代码来打开蓝牙设备。如果设备驱动代码没有问题的话,我们enableNative()返回的将会是true。在实际调试蓝牙设备时候,我们可以通过在linux或者android的终端下面使用自带的工具命令(hciconfig),执行:

# hciconfig –a如果驱动能够和设备绑定的话,我们就会看到蓝牙设备的一些比较重要信息,如:蓝牙的物理地址,总线类型,协议类型等。

上面的代码接下来会是一个while循环,执行2次。mEventLoop.start()。也就是说调用了EventLoop的start方法。

/framework/base/core/java/android/server/BluetoothEventLoop.java

/* package */ void start() {

if (!isEventLoopRunningNative()) {

if (DBG) log("Starting Event Loop thread");

startEventLoopNative();

}

}

第一次进入这个函数isEventLoopRunningNative肯定是返回false的,所以直接进入了startEventLoopNative(),前面说过了一般带native的函数结尾的函数都是JNI。看到这里又要进JNI了。

Framework/base/core/jni/android_server_BluetoothEventLoop.cpp

static JNINativeMethod sMethods[] = {

……

{"startEventLoopNative", "()V", (void *)startEventLoopNative},

……

}

static jboolean startEventLoopNative(JNIEnv *env, jobject object) {

……

nat->pollData = (struct pollfd *)malloc(sizeof(struct pollfd) *

DEFAULT_INITIAL_POLLFD_COUNT);

……

nat->watchData = (DBusWatch **)malloc(sizeof(DBusWatch *) *

DEFAULT_INITIAL_POLLFD_COUNT);

……

if (socketpair(AF_LOCAL, SOCK_STREAM, 0, &(nat->controlFdR))) {

LOGE("Error getting BT control socket");

goto done;

}

……

if (setUpEventLoop(nat) != JNI_TRUE) {

LOGE("failure setting up Event Loop!");

goto done;

}

pthread_create(&(nat->thread), NULL, eventLoopMain, nat);

……

}

为socket文件描述符分配内存数据,同时为DBusWatch结构体分配内存,socketpair创建了一对套接字(AF_LOCAL域中使用),这个描述符可以是单双工也可以是全双工的,这里是单双工的,也就是只能从这个描述符中读取数据,而不能写数据。如果socketpair的第四个参数是个数组,也可以实现一个描述符读,另外一个描述符写。从而实现全双工。然后就是setUpEventLoop函数,最后就是创建了eventLoopMain的线程。

Framework/base/core/jni/android_server_BluetoothEventLoop.cpp

static jboolean setUpEventLoop(native_data_t *nat) {

……

dbus_threads_init_default();

……

dbus_error_init(&err);

……

if (!dbus_connection_add_filter(nat->conn, event_filter, nat, NULL)){

return JNI_FALSE;

}

……

dbus_bus_add_match(nat->conn,

"type='signal',interface='org.freedesktop.DBus'", &err);

dbus_bus_add_match(nat->conn,

"type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Adapter'",&err);

……

}

这里是初始化dbus,是bluez能够挂接上dbus。建立一个dbus连接之后,为这个dbus连接起名,为我们将要进行的消息循环添加匹配条件(就是通过信号名和信号接口名来进行匹配控制的) -- dbus_bus_add_match()。我们进入等待循环后,只需要对信号名,信号接口名进行判断就可以分别处理各种信号了。在各个处理分支上。我们可以分离出消息中的参数。对参数类型进行判断和其他的处理。具体对dbus感兴趣的话可以参照:http://dbus.freedesktop.org

Framework/base/core/jni/android_server_BluetoothEventLoop.cpp

static void *eventLoopMain(void *ptr) {

……

while (1) {

……

if (nat->pollData[i].fd == nat->controlFdR) {

while (recv(nat->controlFdR, &data, sizeof(char), MSG_DONTWAIT) != -1) {

……

switch (data) {

case EVENT_LOOP_EXIT:

dbus_connection_set_watch_functions(nat->conn,NULL, NULL, NULL, NULL, NULL);

tearDownEventLoop(nat);

nat->vm->DetachCurrentThread();

……

case EVENT_LOOP_ADD:

{

handleWatchAdd(nat);

break;

}

case EVENT_LOOP_REMOVE:

{

handleWatchRemove(nat);

break;

}

以轮训的方式从socket的描述符中不断的接收数据,如果有数据到来,就根据数据的类型做相应的处理。到这里dbus就和bluez建立连接。还是回到之前我们prepareBluetooth的函数。

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private class PowerOff extends State {

public void enter() {

if (DBG) log("Enter PowerOff: " + getCurrentMessage().what);

}

……

case USER_TURN_ON:

broadcastState(BluetoothAdapter.STATE_TURNING_ON);

transitionTo(mWarmUp);

……

if (prepareBluetooth()) {

if ((Boolean) message.obj) {

persistSwitchSetting(true);

}

deferMessage(obtainMessage(TURN_ON_CONTINUE));

前面的代码我们分析完了prepareBluetooth(),如果没有问题就进入了persistSwitchSetting()。

然后就是讲蓝牙状态机切换到mWarnUp状态。并向蓝牙状态机发送了一个TURN_ON_CONTINUE的命令。

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private class WarmUp extends State {

……

public boolean processMessage(Message message) {

log("WarmUp process message: " + message.what);

……

switch(message.what) {

case TURN_ON_CONTINUE:

这个命令在WarmUp状态里面什么也没做。直接通过deferMessage()到HotOff状态里面重新发送了TURN_ON_CONTINUE的命令。那我们HotOff状态机。

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

private class HotOff extends State {

……

public boolean processMessage(Message message) {

log("HotOff process message: " + message.what);

……

switch(message.what) {

case TURN_ON_CONTINUE:

int retryCount = 5

……

mBluetoothService.switchConnectable(true);

transitionTo(mSwitching);

……

}

在HotOff状态机中,接收到TURN_ON_CONTINUE命令后,先调用了BluetoothService的switchConnectable(true);然后将蓝牙的状态机切换到Switching状态。

/framework/base/core/java/android/server/BluetoothService.java

/*package*/ synchronized void switchConnectable(boolean on) {

setAdapterPropertyBooleanNative("Powered", on ? 1 : 0);

}

又到了以Native结尾的函数,还是到JNI里面找到它的实现吧。

Framework/base/core/jni/android_server_BluetoothService.cpp

static jboolean setAdapterPropertyNative(JNIEnv *env, jobject object, jstring key,

void *value, jint type) {

……

msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,get_adapter_path(env, object),

DBUS_ADAPTER_IFACE, "SetProperty");

……

dbus_message_append_args(msg, DBUS_TYPE_STRING, &c_key, DBUS_TYPE_INVALID);

dbus_message_iter_init_append(msg, &iter);

……

reply = dbus_connection_send_with_reply(nat->conn, msg, NULL, -1);

……

}

通过Dbus向bluez发送SetPropery的信息(message),当成功的时候,我们在另外一端就会收到powerChanged的信号。具体处理如下的代码:

Framework/base/core/java/android/server/BluetoothEventLoop.java

/*package*/ void onPropertyChanged(String[] propValues) {

……

BluetoothAdapterProperties adapterProperties = mBluetoothService.getAdapterProperties();

……

else if (name.equals("Pairable") || name.equals("Discoverable")) {

adapterProperties.setProperty(name, propValues[1]);

if (name.equals("Discoverable")) {

mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED);

}

……

else if (name.equals("Powered")) {

mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,

propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));

当有蓝牙AdapterProperies发生变化时,在BluetoothEventLoop.java中就会有个onProperyCha

nged方法来处理。首先通过BluetoothService的getAdapterProperties来获取蓝牙适配器的所有属性,都有哪些属性,在实际开发过程中我们通过调试可以看到按顺序依次是:power,

Pairable,class,device,UUID,Discoverable。通过将power的value设置为true,就会向蓝牙状态发送一个POWER_STATE_CHAGED,通过Discoverable的属性来向蓝牙状态机发送一个

SCAN_MODE_CHANGED的命令。

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

case POWER_STATE_CHANGED:

removeMessages(POWER_DOWN_TIMEOUT);

if (!((Boolean) message.obj)) {

if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) {

transitionTo(mHotOff);

finishSwitchingOff();

if (!mContext.getResources().getBoolean

(com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {

deferMessage(obtainMessage(TURN_COLD));

}

}

} else {

if (mPublicState != BluetoothAdapter.STATE_TURNING_ON) {

if (mContext.getResources().getBoolean

(com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {

recoverStateMachine(TURN_HOT, null);

} else {

recoverStateMachine(TURN_COLD, null);

}

}

}

在上一个HotOff的时候,已经将蓝牙状态机切换到了Switching了。所以直接在Switching这个状态里面来处理命令。第一个power_state_changed的命令很简单。在蓝牙状态机里面有个叫mPublicState的全局变量来记录蓝牙适配器的状态。如果是power的值为true,那么就将这个变量的值变为STATE_TURNING_ON,否则就是STATE_TURNING_OFF。在前面介绍过了蓝牙适配器总共有四个状态:State_off(10),state_turning_on(11),state_on(12),state_turning_off(

13)。那么继续来看第二个命令,scan_mode_changed。

/framework/base/core/java/android/server/BluetoothAdapterStateMachine.java

case SCAN_MODE_CHANGED:

if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) {

mBluetoothService.setPairable();

mBluetoothService.initBluetoothAfterTurningOn();

transitionTo(mBluetoothOn);

broadcastState(BluetoothAdapter.STATE_ON);

mBluetoothService.runBluetooth();

}

根据第一个命令,mPublicState的值是STATE_TURNING_ON,这里又要和BluetoothService来交互了,先调用了setPairable和initBluetoothAfterTurningOn,runBluetooth并将蓝牙状态机切换到BluetoothOn的状态。接下来到bluetoothService看这个setPairable方法。

/framework/base/core/java/android/server/BluetoothService.java

/*package*/ synchronized void setPairable() {

String pairableString = getProperty("Pairable", false);

if (pairableString == null) {

Log.e(TAG, "null pairableString");

return;

}

if (pairableString.equals("false")) {

setAdapterPropertyBooleanNative("Pairable", 1);

}

}

这个过程和上面的设置POWER的过程是类似的,先通过getPropery获取Pairable的状态,如果是false的话,就需要调用JNI的方法setAdapterPropertyBooleanNative来通过dbus来向bluez来设置蓝牙适配器的Pairable的值。如果设置成功的话,同样还会调用BluetoothEventLoop中的onProperyChanged方法。继续跟进代码initBluetoothAfterTurningOn:

framework/base/core/java/android/server/BluetoothService.java

/*package*/ void initBluetoothAfterTurningOn() {

String discoverable = getProperty("Discoverable", false);

String timeout = getProperty("DiscoverableTimeout", false);

if (discoverable.equals("true") && Integer.valueOf(timeout) != 0) {

setAdapterPropertyBooleanNative("Discoverable", 0);

}

mBondState.initBondState();

initProfileState();

getProfileProxy();

}

这个函数首先还是像设置power,Parirable的属性差不多,设置Discoverable的属性。当蓝牙模块打开和蓝牙适配器配对(Pairable)之后。剩下的initProfileState可以获取蓝牙的物理地址。

getProfileProxy直接调用了Adapter的getProfileProxy。得到俄ProfileProxy可以是HEADSET,

A2DP,INPUT_DEVICE,PAN,HEALTH。

framework/base/core/java/android/server/BluetoothService.java

/*package*/ void runBluetooth() {

……

autoConnect();

}

private void autoConnect() {

String[] bonds = getKnownDevices();

if (bonds == null) {

return;

}

……

for (String path : bonds) {

String address = getAddressFromObjectPath(path);

BluetoothDeviceProfileState state = mDeviceProfileState.get(address);

if (state != null) {

Message msg = new Message();

msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;

state.sendMessage(msg);

}

}

}

在autoConnect中,就会扫描附近的设备,并获取设备的地址和名字。这是我们看到就是能看到了一系列扫描出来的附近的设备。此时蓝牙的状态出于正常运行。到这里蓝牙模块就在

Android中工作起来了。

1.4 蓝牙开发在android中的调试

1.4.1 内核和驱动的支持

作为是linux内核的Android系统,必须在编译内核过程中将bluez编译的config选上。

CONFIG_BT =y

CONFIG_BT_RFCOMM =y

CONFIG_BT_BNEP = y

CONFIG_BT_CMTP =y

CONFIG_BT_L2CAP=y

CONFIG_BT_SCO=y

然后根据我们的驱动使用的接入方式,常见的有串口(uart),USB,SDIO总线等。如果我们的驱动能够正常工作工作的话,我们在linux的终端通过下面命令就可以看见hci设备了。

root@android:/ # hciconfig -a

hci0: Type: BR/EDR Bus: USB

BD Address: 74:2F:68:CE:13:57 ACL MTU: 1022:8 SCO MTU: 183:5

DOWN

RX bytes:505 acl:0 sco:0 events:22 errors:0

TX bytes:99 acl:0 sco:0 commands:22 errors:0

Features: 0xff 0xfe 0x0d 0xfe 0xd8 0x7f 0x7b 0x87

Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3

Link policy: RSWITCH HOLD SNIFF

Link mode: SLAVE ACCEPT

如果能够像上面可以看见蓝牙的类型,总线类型,物理地址等信息。说明蓝牙设备已经在内核中注册成功了,但能不能使用还要继续使用下面命令,我们注意到蓝牙模块状态时DOWN的。

root@android:/ # hciconfig hci0 up

root@android:/ # hciconfig -a

hci0: Type: BR/EDR Bus: USB

BD Address: 74:2F:68:CE:13:57 ACL MTU: 1022:8 SCO MTU: 183:5

UP RUNNING

RX bytes:994 acl:0 sco:0 events:42 errors:0

TX bytes:185 acl:0 sco:0 commands:42 errors:0

Features: 0xff 0xfe 0x0d 0xfe 0xd8 0x7f 0x7b 0x87

Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3

Link policy: RSWITCH HOLD SNIFF

Link mode: SLAVE ACCEPT

Name: 'Bluetooth USB Host Controller'

Class: 0x000000

Service Classes: Unspecified

Device Class: Miscellaneous,

HCI Version: 4.0 (0x6) Revision: 0x102

LMP Version: 4.0 (0x6) Subversion: 0x1

Manufacturer: Atheros Communications, Inc. (69)

通过hciconfig hci0 up命令让蓝牙模块的状态从DOWN变成UP状态。这个时候还不能就确定蓝牙驱动是能正常工作的。需要继续看看我们的蓝牙能不能扫描其他的蓝牙设备,如果能够扫描到其他的设备,就可以说明我们的蓝牙设备在内核态是可以正常工作的。

root@android:/ # hcitool scan

Scanning ...

00:1C:26:D5:3E:D6 DAWEIYAN-MOBL

00:1E:4C:F3:BC:FA ZHILONGX-MOBL

00:1F:3A:F1:94:CD JUELIUX-MOBL

00:27:13:D6:66:D9 QWANG29-MOBL2

50:63:13:C7:83:6D YANCHAOY-MOBL

00:1C:26:FD:11:3A ZWANG16X-MOBL

好了如果能够扫描出设备的物理地址和名字的话,那我们的设备在linux 内核态就ok了。

1.4.2 Android Boradconfig和服务的支持

在BoardConfig.mk中添加:BOARD_HAVE_BLUETOOTH := true。因为在framework中的代码很多函数是需要这个宏的,如果这个宏没有打开的话,很多代码是走不过的。在android的添加蓝牙工作必要的服务,Dbus-daemon,bluetoothd,

/init.rc

service dbus /system/bin/dbus-daemon --system --nofork

class main

socket dbus stream 660 bluetooth bluetooth

user bluetooth

group bluetooth net_bt_admin

service bluetoothd /system/bin/bluetoothd -n

class main

socket bluetooth stream 660 bluetooth bluetooth

socket dbus_bluetooth stream 660 bluetooth Bluetooth

service hciattach /system/bin/hciattach 当然这个服务只针对你的蓝牙接入方式是串口的,向我们这里是USB的话,这个服务还是可以省掉的。

service hfag /system/bin/sdptool add --channel=10 HFAG

user bluetooth

group bluetooth net_bt_admin

disabled

oneshot

service hsag /system/bin/sdptool add --channel=11 HSAG

user bluetooth

group bluetooth net_bt_admin

disabled

oneshot

service opush /system/bin/sdptool add --channel=12 OPUSH

user bluetooth

group bluetooth net_bt_admin

disabled

oneshot

service pbap /system/bin/sdptool add --channel=19 PBAP

user bluetooth

group bluetooth net_bt_admin

disabled

oneshot

好了,到这里蓝牙应该就能正常工作了。上面的代码都是基于android 4.0。

转载:原地值不详,这里是2次转载,如果侵犯你的版权,请告知我,及时删除,仅供个人学习之用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
########################BT FM ########################## Download the image using command: 1 # Flash Image Put the board in Flashing mode. Refer below Appendix - 1 cd <your path>/Nvidia_Demo/android_gb_cardhu_os_image sudo ./nvflash --bct flash.bct --setbct --odmdata 0x40080105 --configfile flash.cfg --create --bl bootloader.bin --go #################################################################################################################################################################################################### Appendix - 1 Nvidia Board in Flashing Mode #################################################################################################################################################################################################### 1. Connect the Debug board to Cardhu board. 2. Connect the power supply and Micro USB to Cardhu 3. On the Debug Board Press S12 (FRC RCV), Keeping this pressed Press and release S7 (RESET), Now Release S12. 4. Now Device is in Flashing mode, We can start nvflash command now. #################################################################################################################################################################################################### Appendix - 2 Nvidia Board Keys (On Debug Board) #################################################################################################################################################################################################### 1. S7 (RESET) --> is the RESET button. 2. S5 (ROW1) --> is the BACK button. 2. S10 (ROW2) --> is the Home button. 4. S6 (ON KEY) --> is Wake up button. #################################################################################################################################################################################################### Appendix - 3 Nvidia Board unavailable Keys workaround #################################################################################################################################################################################################### 1.To execute teh specific keys, provide the key inputs from adb shell. Provide the keyevent for the desired key.Refer teh key list below. Eg: for MENU key run adb shell #input keyevent 82 { "STAR", 17 }, { "POUND", 18 }, { "DPAD_UP", 19 }, { "DPAD_DOWN", 20 }, { "DPAD_LEFT", 21 }, { "DPAD_RIGHT", 22 }, { "DPAD_CENTER", 23 }, { "VOLUME_UP", 24 }, { "VOLUME_DOWN", 25 }, { "POWER", 26 }, { "CAMERA", 27 }, { "CLEAR", 28 }, { "HEADSETHOOK", 79 }, { "FOCUS", 80 }, { "PLUS", 81 }, { "MENU", 82 }, { "NOTIFICATION", 83 }, { "SEARCH", 84 }, { "MEDIA_PLAY_PAUSE", 85 }, { "MEDIA_STOP", 86 }, { "MEDIA_NEXT", 87 },
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值