Android bluetooth介紹(兩): android 藍牙源架構和uart 至rfcomm過程


關鍵詞:藍牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM 
版本號:基於android4.2先前版本 bluez
內核:linux/linux3.08
系統:android/android4.1.3.4
作者:xubin341719(歡迎轉載,請注明作者。請尊重版權謝謝)
歡迎指正錯誤,共同學習、共同進步!!



Android bluetooth介紹(一):基本概念及硬件接口
Android bluetooth介紹(二): android 藍牙代碼架構及其uart 到rfcomm流程
Android bluetooth介紹(三): 藍牙掃描(scan)設備分析
Android bluetooth介紹(四): a2dp connect流程分析

一、Android Bluetooth Architecture藍牙代碼架構部分(google 官方藍牙框架)


Android的藍牙系統。自下而上包含以下一些內容如上圖所看到的:
1、串口驅動
Linux的內核的藍牙驅動程、Linux的內核的藍牙協議的層
2、BlueZ的適配器
BlueZ的(藍牙在用戶空間的函式庫)

bluez代碼結構
Bluetooth協議棧BlueZ分為兩部分:內核代碼和用戶態程序及工具集。
(1)、內核代碼:由BlueZ核心協議和驅動程序組成
Bluetooth協議實如今內核源碼 kernel/net/bluetooth中。

包含hci,l2cap,hid,rfcomm,sco,SDP,BNEP等協議的實現。
(2)、驅動程序:kernel/driver/bluetooth中,包含Linuxkernel對各種接口的
Bluetooth device的驅動,如:USB接口,串口等。
(3)、用戶態程序及工具集:
包含應用程序接口和BlueZ工具集。BlueZ提供函數庫以及應用程序接口,便於程序猿開發bluetooth應用程序。BlueZ utils是主要工具集,實現對bluetooth設備的初始化和控制。

3、藍牙相關的應用程序接口
Android.buletooth包中的各個Class(藍牙在框架層的內容-----java)

類名

作用

BluetoothAdapter

本地藍牙設備的適配類,全部的藍牙操作都要通過該類完畢

BluetoothClass

用於描寫敘述遠端設備的類型。特點等信息

BluetoothDevice

藍牙設備類,代表了藍牙通訊過程中的遠端設備

BluetoothServerSocket

藍牙設備服務端,相似ServerSocket

BluetoothSocket

藍牙設備client。相似Socket

BluetoothClass.Device

藍牙關於設備信息

BluetoothClass.Device.Major

藍牙設備管理

BluetoothClass.Service

藍牙相關服務

相同下圖也是一張比較經典的藍牙代碼架構圖(google官方提供)


二、藍牙通過Hciattach啟動串口流程:
1、hciattach整體流程


2、展訊hciattach代碼實現流程:

三、詳細代碼分析
1、initrc中定義

idh.code\device\sprd\sp8830ec_nwcn\init.sc8830.rc

service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
    socket bluetooth stream 660 bluetooth bluetooth
    user bluetooth
    group wifi bluetooth net_bt_admin net_bt inet net_raw net_admin system
    disabled
oneshot

adb 下/dev/ttybt0(不同平台有所不同)

PS 進程中:hicattch

2、/system/bin/hciattach 執行的Main函數
idh.code\external\bluetooth\bluez\tools\hciattach.c
service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
傳進兩個參數,/dev/sttybt0 和 sprd_shark

nt main(int argc, char *argv[])
{
………………
	for (n = 0; optind < argc; n++, optind++) {
		char *opt;

		opt = argv[optind];

		switch(n) {
		case 0://(1)、解析驅動的位置;
			dev[0] = 0;
			if (!strchr(opt, '/'))
				strcpy(dev, "/dev/");
			strcat(dev, opt);
			break;

		case 1://(2)、解析串口的配置相關參數。
			if (strchr(argv[optind], ',')) {
				int m_id, p_id;
				sscanf(argv[optind], "%x,%x", &m_id, &p_id);
				u = get_by_id(m_id, p_id);
			} else {
				u = get_by_type(opt);
			}

			if (!u) {
				fprintf(stderr, "Unknown device type or id\n");
				exit(1);
			}

			break;

		case 2://(3)、通過對前面參數的解析,把uart[i]中的數值初始化;
			u->speed = atoi(argv[optind]);
			break;

		case 3:
			if (!strcmp("flow", argv[optind]))
				u->flags |=  FLOW_CTL;
			else
				u->flags &= ~FLOW_CTL;
			break;

		case 4:
			if (!strcmp("sleep", argv[optind]))
				u->pm = ENABLE_PM;
			else
				u->pm = DISABLE_PM;
			break;

		case 5:
			u->bdaddr = argv[optind];
			break;
		}
	}

………………
	if (init_speed)//初始化串口速率;
		u->init_speed = init_speed;
………………
	n = init_uart(dev, u, send_break, raw);//(4)、初始化串口;
………………

	return 0;
}

(1)、解析驅動的位置。

			if (!strchr(opt, '/'))
				strcpy(dev, "/dev/");
service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
dev = /dev/ttyb0

(2)、解析串口的配置相關參數;獲取參數相應的結構體。

	u = get_by_id(m_id, p_id);
static struct uart_t * get_by_id(int m_id, int p_id)
{
	int i;
	for (i = 0; uart[i].type; i++) {
		if (uart[i].m_id == m_id && uart[i].p_id == p_id)
			return &uart[i];
	}
	return NULL;
}

這個函數比較簡單,通過循環對照。如傳進了的參數sprd_shark和uart結構體中的對照,找到相應的數組。假設是其它藍牙芯片,如博通、RDA、BEKN等着到其相相應的初始化配置函數。

struct uart_t uart[] = {
	{ "any",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
				FLOW_CTL, DISABLE_PM, NULL, NULL     },
	{ "sprd_shark",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
				FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },

	{ "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200,
				FLOW_CTL, DISABLE_PM, NULL, ericsson },

………………
	{ "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},
	{ NULL, 0 }
};

注意:init_sprd_config這個函數在uart_init中用到,這個函數事實上對我們詳細芯片的初始化配置。
凝視:HCI_UART_H4和HCI_UART_BCSP的差別例如以下圖。

(3)、通過對前面參數的解析,把uart[i]中的數值初始化;

			u->speed = atoi(argv[optind]);
			break;

(4)、初始化串口;

n = init_uart(dev, u, send_break, raw);
idh.code\external\bluetooth\bluez\tools\hciattach.c
/* Initialize UART driver */
int init_uart(char *dev, struct uart_t *u, int send_break)
{
 struct termios ti;
 int  fd, i;
 fd = open(dev, O_RDWR | O_NOCTTY);//打開串口設備,當中標志
//O_RDWR,能夠對此設備進行讀寫操作。
//O_NOCTTY:告訴Unix這個程序不想成為“控制終端”控制的程序,不說明這個標志的話,不論什么輸入都會影響你的程序。

//O_NDELAY:告訴Unix這個程序不關心DCD信號線狀態。即其它端口是否執行。不說明這個標志的話,該程序就會在DCD信號線為低電平時停止。 //可是不要以控制 tty 的模式,由於我們並不希望在發送 Ctrl-C 后結束此進程 if (fd < 0) { perror(“Can’t open serial port”); return -1; } //drop fd’s data; tcflush(fd, TCIOFLUSH);//清空數據線 if (tcgetattr(fd, &ti) < 0) { perror(“Can’t get port settings”); return -1; } cfmakeraw(&ti); cfmakeraw sets the terminal attributes as follows://此函數設置串口終端的以下這些屬性, termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP |INLCR|IGNCR|ICRNL|IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); termios_p->c_cflag &= ~(CSIZE|PARENB) ; termios_p->c_cflag |=CS8; ti.c_cflag |= CLOCAL;//本地連接,無調制解調器控制 if (u->flags & FLOW_CTL) ti.c_cflag |= CRTSCTS;//輸出硬件流控 else ti.c_cflag &= ~CRTSCTS; if (tcsetattr(fd, TCSANOW, &ti) < 0) {//啟動新的串口設置 perror(“Can’t set port settings”); return -1; } /* Set initial baudrate */ if (set_speed(fd, &ti, u->init_speed) < 0) {//設置串口的傳輸速率bps, 也能夠使 //用 cfsetispeed 和 cfsetospeed 來設置 perror(“Can’t set initial baud rate”); return -1; } tcflush(fd, TCIOFLUSH);//清空數據線 if (send_break) tcsendbreak(fd, 0); //int tcsendbreak ( int fd, int duration );Sends a break for //the given time.在串口線上發送0值,至少維持0.25秒。 //If duration is 0, it transmits zero-valued bits for at least 0.25 seconds, and //not more than 0.5seconds. //where place register u’s init function; if (u->init && u->init(fd, u, &ti) < 0) //全部bluez支持的藍牙串口設備類型構成了一個uart結構數組,通過 //查找相應的uart類型。這個uart的init成員顯示了它的init調用方法; struct uart_t uart[] = { { "any", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, NULL }, { "sprd_shark", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, init_sprd_config }, { "ericsson", 0x0000, 0x0000, HCI_UART_H4, 57600, 115200,FLOW_CTL, DISABLE_PM, NULL, ericsson }, ……………… { "bk3211", 0x0000, 0x0000, HCI_UART_BCSP, 115200, 921600, 0, DISABLE_PM, NULL, beken_init, NULL}, { NULL, 0的init函數名為bcsp,定義在本文件里**; return -1; tcflush(fd, TCIOFLUSH);//清空數據線 /* Set actual baudrate */ if (set_speed(fd, &ti, u->speed) < 0) { perror(“Can’t set baud rate”); return -1; } /* Set TTY to N_HCI line discipline */ i = N_HCI; if (ioctl(fd, TIOCSETD, &i) < 0) {// TIOCSETD int *ldisc//改變到 i 行規。即hci行規 Change to the new line discipline pointed to by ldisc. The available line disciplines are listed in /* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */ /* line disciplines */ #define N_TTY 0 …… #define N_HCI 15 /* Bluetooth HCI UART */ perror(“Can’t set line discipline”); return -1; } if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) { //設置hci設備的proto操作函數集為hci_uart操作集; perror(“Can’t set device”); return -1; } return fd; }

這里一個重要的部分是:u->init指向init_sprd_config
4、uart詳細到芯片的初始化init_sprd_config(這部分依據不同的芯片,相應進入其相應初始化部分)
idh.code\external\bluetooth\bluez\tools\hciattach_sprd.c

int sprd_config_init(int fd, char *bdaddr, struct termios *ti)
{
	int i,psk_fd,fd_btaddr,ret = 0,r,size=0,read_btmac=0;
	unsigned char resp[30];
	BT_PSKEY_CONFIG_T bt_para_tmp;
	char bt_mac[30] = {0};
	char bt_mac_tmp[20] = {0};
	uint8 bt_mac_bin[32]     = {0};

	fprintf(stderr,"init_sprd_config in \n");
//(1)、這部分檢查bt_mac,假設存在,從文件里讀取,假設不存在,隨機生成。並寫入相應文件;
	if(access(BT_MAC_FILE, F_OK) == 0) {//這部分檢查bt_mac
		LOGD("%s: %s exists",__FUNCTION__, BT_MAC_FILE);
		fd_btaddr = open(BT_MAC_FILE, O_RDWR);// #define BT_MAC_FILE		"/productinfo/btmac.txt"
		if(fd_btaddr>=0) {
			size = read(fd_btaddr, bt_mac, sizeof(bt_mac));//讀取BT_MAC_FILE中的地址。
			LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);
			if(size == BT_RAND_MAC_LENGTH){
						LOGD("bt mac already exists, no need to random it");
						fprintf(stderr, "read btmac ok \n");
						read_btmac=1;
			}
…………
	}else{//假設不存在,就隨機生成一個bt_mac地址。寫入/productinfo/btmac.txt
		fprintf(stderr, "btmac.txt not exsit!\n");
		read_btmac=0;
		mac_rand(bt_mac);
		LOGD("bt random mac=%s",bt_mac);
		printf("bt_mac=%s\n",bt_mac);
		write_btmac2file(bt_mac);

		fd_btaddr = open(BT_MAC_FILE, O_RDWR);
		if(fd_btaddr>=0) {
			size = read(fd_btaddr, bt_mac, sizeof(bt_mac));
			LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);
			if(size == BT_RAND_MAC_LENGTH){
						LOGD("bt mac already exists, no need to random it");
						fprintf(stderr, "read btmac ok \n");
						read_btmac=1;
			}
			close(fd_btaddr);
…………
	}

	/* Reset the BT Chip */

	memset(resp, 0, sizeof(resp));
	memset(&bt_para_tmp, 0, sizeof(BT_PSKEY_CONFIG_T) );
	ret = getPskeyFromFile(  (void *)(&bt_para_tmp) );//ret = get_pskey_from_file(&bt_para_tmp);//(2)、PSKey參數、射頻參數的設定。
       if(ret != 0){//參數失敗處理
			fprintf(stderr, "get_pskey_from_file faill \n");
			/* Send command from hciattach*/
			if(read_btmac == 1){
				memcpy(bt_para_setting.device_addr, bt_mac_bin, sizeof(bt_para_setting.device_addr));// (3)、讀取失敗,把bt_para_setting中defaut參數寫入;
			}
			if (write(fd, (char *)&bt_para_setting, sizeof(BT_PSKEY_CONFIG_T)) != sizeof(BT_PSKEY_CONFIG_T)) {
				fprintf(stderr, "Failed to write reset command\n");
				return -1;
			}
        }else{//getpskey成功處理
			/* Send command from pskey_bt.txt*/
			if(read_btmac == 1){
				memcpy(bt_para_tmp.device_addr, bt_mac_bin, sizeof(bt_para_tmp.device_addr));
			}
…………
	return 0;
}

(1)、這部分檢查bt_mac,假設存在,從文件里讀取,假設不存在。隨機生成。並寫入相應文件/productinfo/btmac.txt
(2)、PSKey參數、射頻參數的設定;
get_pskey_from_file(&bt_para_tmp);這個函數后面分析;
(3)、讀取失敗,把bt_para_setting中defaut參數寫入;頻率、主從設備設定等……

// pskey file structure default value
BT_PSKEY_CONFIG_T bt_para_setting={
5,
0,
0,
0,
0,
0x18cba80,
0x001f00,
0x1e,
{0x7a00,0x7600,0x7200,0x5200,0x2300,0x0300},
…………
};

5、get_pskey_from_file 解析相關射頻參數
idh.code\external\bluetooth\bluez\tools\pskey_get.c

int getPskeyFromFile(void *pData)
{
…………
        char *BOARD_TYPE_PATH = "/dev/board_type";//(1)、推斷PCB的版本號;
        int fd_board_type;
        char board_type_str[MAX_BOARD_TYPE_LEN] = {0};
        int board_type;
        char *CFG_2351_PATH_2 = "/productinfo/2351_connectivity_configure.ini";//(2)、終於生成ini文件存儲的位置;
        char *CFG_2351_PATH[MAX_BOARD_TYPE];
		(3)、針對不同PCB版本號。不同的ini配置文件。
        CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";
        CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";
        CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";

(4)、以下函數就不做詳細分析。大致意識是。依據/dev/board_type中,讀取的PCB類型,設置不同的ini文件。

  

………………
	ret = chmod(CFG_2351_PATH_2, 0644);
	ALOGE("chmod 0664 %s ret:%d\n", CFG_2351_PATH_2, ret);	
	if(pBuf == pBuf2)
		free(pBuf1);
………………
}

(1)、推斷PCB的版本號;
char *BOARD_TYPE_PATH = "/dev/board_type";

(2)、終於生成ini文件存儲的位置,就是系統執行時讀取ini文件的地方。
char *CFG_2351_PATH_2 ="/productinfo/2351_connectivity_configure.ini";
(3)、針對不同PCB版本號。不同的ini配置文件。

        CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";
        CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";
        CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";

(4)、以下函數就不做詳細分析,大致意識是,依據/dev/board_type中,讀取的PCB類型,設置不同的ini文件。         覆蓋到(2)中的文件。
四、HCI_UART_H4和H4層的增加

uart->hci_uart->Uart-H4->hci:從uart開始分析,介紹整個驅動層數據流(涉及tty_uart中斷,   線路層ldisc_bcsp、tasklet、work queue、skb_buffer的等)

這是數據的流動過程,最底層的也就是和硬件打交道的是uart層了,它的存在和起作用是通過串口驅動來保證的。這個請參閱附錄,可是其它的層我們都不知道什么時候work的,以下來看。

1、idh.code\kernel\drivers\bluetooth\hci_ldisc.c

static int __init hci_uart_init(void)
{
	static struct tty_ldisc_ops hci_uart_ldisc;
	int err;
	/* Register the tty discipline */

	memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc));
	hci_uart_ldisc.magic		= TTY_LDISC_MAGIC;
	hci_uart_ldisc.name		= "n_hci";
	hci_uart_ldisc.open		= hci_uart_tty_open;
	hci_uart_ldisc.close		= hci_uart_tty_close;
	hci_uart_ldisc.read		= hci_uart_tty_read;
	hci_uart_ldisc.write		= hci_uart_tty_write;
	hci_uart_ldisc.ioctl		= hci_uart_tty_ioctl;
	hci_uart_ldisc.poll		= hci_uart_tty_poll;
	hci_uart_ldisc.receive_buf	= hci_uart_tty_receive;
	hci_uart_ldisc.write_wakeup	= hci_uart_tty_wakeup;
	hci_uart_ldisc.owner		= THIS_MODULE;

	if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) {//(1)、這部分完畢ldisc的注冊;
		BT_ERR("HCI line discipline registration failed. (%d)", err);
		return err;
	}

#ifdef CONFIG_BT_HCIUART_H4
	h4_init();//(2)、我們藍牙芯片用的是H4。這部分完畢H4的注冊;
#endif
#ifdef CONFIG_BT_HCIUART_BCSP
	bcsp_init();
#endif
………………
	return 0;
}

(1)、這部分完畢ldisc的注冊。
tty_register_ldisc(N_HCI,&hci_uart_ldisc)
注冊了一個ldisc。這是通過把新的ldisc放在一個ldisc的數組里面實現的,tty_ldiscs是一個全局的ldisc數組里面會依據序號相應一個ldisc,這個序號就是上層通過ioctl來指定的。比方我們在前面已經看到的:
i = N_HCI;
ioctl(fd, TIOCSETD, &i) < 0
能夠看到這里指定的N_HCI剛好就是這里注冊的這個號碼15;
(2)、藍牙芯片用的是H4,這部分完畢H4的注冊。
         h4_init();
hci_uart_proto結構體的初始化:

idh.code\kernel\drivers\bluetooth\hci_h4.c

static struct hci_uart_proto h4p = {
	.id		= HCI_UART_H4,
	.open		= h4_open,
	.close		= h4_close,
	.recv		= h4_recv,
	.enqueue	= h4_enqueue,
	.dequeue	= h4_dequeue,
	.flush		= h4_flush,
};

H4的注冊:
idh.code\kernel\drivers\bluetooth\hci_h4.c

int __init h4_init(void)
{
	int err = hci_uart_register_proto(&h4p);

	if (!err)
		BT_INFO("HCI H4 protocol initialized");
	else
		BT_ERR("HCI H4 protocol registration failed");

	return err;
}

這是通過hci_uart_register_proto(&bcsp)來完畢的,這個函數非常easy。本質例如以下:
hup[p->id]= p;當中static struct hci_uart_proto*hup[HCI_UART_MAX_PROTO];也就是說把相應於協議p的id和協議p連接起來。這樣設計的優點是hci uart層本身能夠支持不同的協議。包含h4、bcsp等,通過這個數組連接這些協議,等以后有數據的時候調用相應的協議來處理。這里比較關鍵的是h4里面的這些函數。
五、HCI層的增加
hci的增加是通過hci_register_dev函數來做的,這時候用戶通過hciconfig就能夠看到有一個接口了,通過這個接口用戶能夠訪問底層的信息了。hci0已經生成;至於它在何時被增加的,我們再看看hciattach在內核里面的處理過程;

1、TIOCSEATD的處理流程

Ioctl的作用是設置一個新的ldisc;
2、HCIUARTSETPROTO的處理流程:

這部分比較重要,注冊生成hci0, 初始化3個工作隊列,hci_rx_work、hci_tx_work、hci_cmd_work;完畢hci部分數據、命令的接收、發送。
六、數據在驅動的傳遞流程
1、uart數據接收
         這部分流程比較簡單。事實上就是注冊一個tty驅動程序和相相應的函數,注冊相應的open\close\ioctl等方法。通過應用open /dev/ttyS*操作,注冊中斷接收函數,接收處理藍牙模塊觸發中斷的數據。



在這個中斷函數里面會接受到來自於藍牙模塊的數據;在中斷函數里面會先讀取串口的狀態寄存器推斷是否是data准備好,假設准備好就調用serial_sprd_rx_chars函數來接收數據。以下看看這個函數是怎樣處理的:

那就是把數據一個個的增加到uart層的緩沖區,直究竟層不處於dataready狀態,或者讀了maxcount個數,當讀完后就調用tty層的接口把數據傳遞給tty層。tty層則把數據交給了ldisc,於是控制權也就交給了hci_uart層;

七、Hci_uart的數據接收
它基本上就是要個二傳手,通過:

         spin_lock(&hu->rx_lock);
         hu->proto->recv(hu,(void *) data, count);
         hu->hdev->stat.byte_rx+= count;
         spin_unlock(&hu->rx_lock);
把數據交給了在它之上的協議層,對於我們的設置來說實際上就交給了h4層;
八、H4層處理
這層主要是通過函數h4_recv來處理的,依據協議處理包頭、CRC等,然后調用更上層的hci_recv_frame來處理已經剝去h4包頭的數據。

如圖:

九、HCI以上的處理

這里的hci_rx_work前面已經看到它了。它是一個工作隊列用來處理hci層的數據接收的。先看是否有進程打開hci的socket用來監聽數據,假設有的話,就把數據的一個copy發送給它。然后依據包的類型調用不同的處理函數。分別相應於event、acl、sco處理;
hci_event_packet是對於事件的處理。里面包含有包含掃描。信號,授權,pin碼,總之基本上上層所能收到的事件,基本都是在這里處理的,它的非常多信息都是先存起來。等待上層的查詢然后才告訴上層。
hci_acldata_packet是一個常常的情況,也就是說上層通常都是使用的是l2cap的接口。而l2cap就是基於這個的,例如以下圖所看到的:

到這里假設有基於BTPROTO_L2CAP的socket,那么這個socket就能夠收到數據了;再看看BTPROTO_RFCOMM的流程:

十、 數據流程的總結
簡單總結一下,數據的流程,
|基本上是:
1, uart口取得藍牙模塊的數據;
2。 uart口通過ldisc傳給hci_uart。
3。 hci_uart傳給在其上的h4;
4。 h4傳給hci層。
5。 hci層傳給l2cap層
6, l2cap層,然后通rfcomm。

版權聲明:本文博客原創文章,博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM