zoukankan      html  css  js  c++  java
  • 第07节-开源蓝牙协议BTStack框架代码阅读(上)

    首先来看一下,对于硬件操作,它是如何来进行处理的。在上篇文章中曾说过,在main函数里面它会调用硬件相关的代码,调用操作系统相关的代码。在BTStack中,可以搜索一下main.c,将会发现有很多main.c,都是为于port目录下面。

     1 Main.c (portesp32componentststack)
     2 Main.c (portez430-rf2560src)    
     3 Main.c (portlibusb)    
     4 Main.c (portlibusb-intel)    
     5 Main.c (portmax32630-fthrsrc)    
     6 Main.c (portmsp-exp430f5438-cc2564bsrc)    
     7 Main.c (portmsp430f5229lp-cc2564bsrc)    
     8 Main.c (port
    rf5-zephyr)    
     9 Main.c (port
    rf5x)    
    10 Main.c (portpic32-harmonysrc)    
    11 Main.c (portposix-h4)    
    12 Main.c (portposix-h4-atwilc3000)    
    13 Main.c (portposix-h4-da14581)    
    14 Main.c (portposix-h4-da14585)    
    15 Main.c (portposix-h4-zephyr)    
    16 Main.c (portposix-h5)    
    17 Main.c (portposix-h5-bcm)    
    18 Main.c (port
    aspi)    
    19 Main.c (portsamv71-xplained-atwilc3000)    
    20 Main.c (portstm32-f103rb-nucleo)    
    21 Main.c (portstm32-f4discovery-cc256xeclipse-templatesrc)    
    22 Main.c (portstm32-l053r8-em9304cubemx-l053r8-em9304src)    
    23 Main.c (portwiced-h4)    
    24 Main.c (portwiced-h5)    
    25 Main.c (portwindows-h4)    
    26 Main.c (portwindows-h4-zephyr)    
    27 Main.c (portwindows-winusb)    
    28 Main.c (portwindows-winusb-intel)    
    View Code

    看一下windows,有Main.c (portwindows-h4)、Main.c (portwindows-winusb),使用的是usb口的蓝牙模块。注意后h4表示5线串口的蓝牙模块。

    分析Main.c 中的main函数,按照上一篇文章中总结出来的框架,首先找到硬件操作的相关代码,然后再看操作系统先关的代码

    1. 硬件相关的代码:

    a.使用usb口

    分析Main.c (portwindows-winusb)

    // setup USB Transport
    transport = hci_transport_usb_instance();

    const hci_transport_t * hci_transport_usb_instance(void) {
      return &hci_transport_usb;  //返回hci_transport_usb的结构体
    }

    hci_transport_usb的结构体定义如下:

     1 // get usb singleton
     2 static const hci_transport_t hci_transport_usb = {
     3   /* const char * name; */ "H2_WINUSB",
     4   /* void (*init) (const void *transport_config); */ &usb_init,
     5   /* int (*open)(void); */ &usb_open,
     6   /* int (*close)(void); */ &usb_close,
     7   /* void (*register_packet_handler)(void (*handler)(...); */ &usb_register_packet_handler,
     8   /* int (*can_send_packet_now)(uint8_t packet_type); */ &usb_can_send_packet_now,
     9   /* int (*send_packet)(...); */ &usb_send_packet,
    10   /* int (*set_baudrate)(uint32_t baudrate); */ NULL,
    11   /* void (*reset_link)(void); */ NULL,
    12 #ifdef ENABLE_SCO_OVER_HCI
    13   /* void (*set_sco_config)(uint16_t voice_setting, int num_connections); */ usb_set_sco_config, 
    14 #else
    15   /* void (*set_sco_config)(uint16_t voice_setting, int num_connections); */ NULL, 
    16 #endif 
    17 };
    View Code

    在hci_transport_usb结构体中,有初始化函数、有open函数、有注册函数、有发送包的函数等等,这些函数应该就是用来操作硬件的。

    在main函数中,返回了一个结构体,以后将使用transport 这个结构体去操作硬件——从硬件里面得到数据或把数据发给硬件。

    以上使用的是USB口,如果我使用的是串口呢?硬件操作的相关代码又是怎样的?

    b. 使用串口

    分析Main.c (portwindows-h4)

    main

      const btstack_uart_block_t * uart_driver = btstack_uart_block_windows_instance();

      const hci_transport_t * transport = hci_transport_h4_instance(uart_driver);  //同样是返回一个transport结构体 

      // configure and return h4 singleton

      const hci_transport_t * hci_transport_h4_instance(const btstack_uart_block_t * uart_driver) {
        btstack_uart = uart_driver;
        return &hci_transport_h4;//返回hci_transport_h4的结构体
      }

    hci_transport_h4结构体是什么样的呢?定义如下:

     1 static const hci_transport_t hci_transport_h4 = {
     2     /* const char * name; */                                        "H4",
     3     /* void   (*init) (const void *transport_config); */            &hci_transport_h4_init,
     4     /* int    (*open)(void); */                                     &hci_transport_h4_open,
     5     /* int    (*close)(void); */                                    &hci_transport_h4_close,
     6     /* void   (*register_packet_handler)(void (*handler)(...); */   &hci_transport_h4_register_packet_handler,
     7     /* int    (*can_send_packet_now)(uint8_t packet_type); */       &hci_transport_h4_can_send_now,
     8     /* int    (*send_packet)(...); */                               &hci_transport_h4_send_packet,
     9     /* int    (*set_baudrate)(uint32_t baudrate); */                &hci_transport_h4_set_baudrate,
    10     /* void   (*reset_link)(void); */                               NULL,
    11     /* void   (*set_sco_config)(uint16_t voice_setting, int num_connections); */ NULL, 
    12 };
    View Code

    注意:btstack_uart这个参数是用来干嘛的呢?

    BTStack支持多种接口的蓝牙模块,比如USB口、3线串口、5线串口。对于3线串口和5线串口,它们之间有什么差别呢?

    对于3线串口,它只有三条线:TxD、RxD、GND。5线串口比三线串口多了两条线:CTS、RTS,用来控制流量。
    使用三线串口和无线串口传输同一个数据时,它们使用的协议不一样。

     假设图中红色的部分就是要发送的数据,当使用三线串口时可能给它加上头部、尾部后再发送给硬件,当使用五线串口时可能将数据直接发给硬件。

    从这个地方可以产出,无论是三线串口还是五线串口,它们的底层硬件操作都是一样的。因此在硬件的这一层,又抽象出了一个结构体:uart_driver。使用该结构体来操作硬件。

    H5协议只是将数据加上各种头部和各种尾部,H4协议也只是对数据进行了某种处理。

    因此在main函数中,首先得到了uart_driver,然后再将该结构体作为hci_transport_h4_instance的参数传进去。

    看一下hci_transport_h4_open()函数:

    hci_transport_h4_open

       btstack_uart->open();//直接调用了btstack_uart的open函数。

    从中可以看出,H4、H5协议通过 btstack_uart_block_t结构体来操作硬件。

    2. 操作系统相关的代码

    a.windows操作系统

    分析Main.c (portwindows-winusb)

    main

      btstack_run_loop_init(btstack_run_loop_windows_get_instance());

    通过btstack_run_loop_windows_get_instance()来获取一个结构体,

    /**
    * Provide btstack_run_loop_windows instance
    */
    const btstack_run_loop_t * btstack_run_loop_windows_get_instance(void){
      return &btstack_run_loop_windows;
    }

    btstack_run_loop_windows结构体定义如下:定义了操作系统先关的循环函数。

     1 static const btstack_run_loop_t btstack_run_loop_windows = {
     2     &btstack_run_loop_windows_init,
     3     &btstack_run_loop_windows_add_data_source,
     4     &btstack_run_loop_windows_remove_data_source,
     5     &btstack_run_loop_windows_enable_data_source_callbacks,
     6     &btstack_run_loop_windows_disable_data_source_callbacks,
     7     &btstack_run_loop_windows_set_timer,
     8     &btstack_run_loop_windows_add_timer,
     9     &btstack_run_loop_windows_remove_timer,
    10     &btstack_run_loop_windows_execute,
    11     &btstack_run_loop_windows_dump_timer,
    12     &btstack_run_loop_windows_get_time_ms,
    13 };
    View Code

    b. linux操作系统

    Main.c (portposix-h4)

    main

      btstack_run_loop_init(btstack_run_loop_posix_get_instance());

    通过btstack_run_loop_posix_get_instance()来获取一个结构体

    /**
    * Provide btstack_run_loop_posix instance
    */
    const btstack_run_loop_t * btstack_run_loop_posix_get_instance(void){
      return &btstack_run_loop_posix;
    }

    btstack_run_loop_posix结构体定义如下:定义了操作系统先关的循环函数。

     1 static const btstack_run_loop_t btstack_run_loop_posix = {
     2     &btstack_run_loop_posix_init,
     3     &btstack_run_loop_posix_add_data_source,
     4     &btstack_run_loop_posix_remove_data_source,
     5     &btstack_run_loop_posix_enable_data_source_callbacks,
     6     &btstack_run_loop_posix_disable_data_source_callbacks,
     7     &btstack_run_loop_posix_set_timer,
     8     &btstack_run_loop_posix_add_timer,
     9     &btstack_run_loop_posix_remove_timer,
    10     &btstack_run_loop_posix_execute,
    11     &btstack_run_loop_posix_dump_timer,
    12     &btstack_run_loop_posix_get_time_ms,
    13 };
    View Code

    3. 在主循环中读取数据、处理数据,它是如何用代码来实现的呢?

    例如:如果我使用H5协议的话,从硬件中得到数据,需要将这个数据的头部去掉,才能得到真正的数据。如果我使用H4协议的话,从硬件中得到的数据就是真正的数据。

    如果我使用usb协议的话,得到的数据又需要作另一种处理。从这个地方就可以看出某种端倪来,什么端倪呢?得到数据和处理数据应该绑定在一起。

    即使用A协议得到数据,就使用process_A来处理;使用B协议来得到数据,就使用process_B来处理。在BTStack中,又抽象处理另外一个结构体,该结构体就是btstack_data_source。

     上面已经提到操作系统相关的代码时,在结构体btstack_run_loop中它有一个函数指针void (*add_data_source)(btstack_data_source_t * data_source),就是给这个循环添加一个数据来源。这个数据来源里面有文件句柄或handle、process函数。以后在循环里面,它可以通过文件句柄或handle中获取数据,得到数据后马上调用它里面的process函数。问题来了,btstack_data_source结构体是在什么时候创建的?显然应该在打开硬件设备时,就会创建这个结构体,并且把这格结构体添加到btstack_run_loop中。

    4.数据如何进行处理的呢?

    process函数就是数据处理的起点,前面已经说过,对数据的处理分为两部分:一部分是蓝牙协议栈中各个层次的处理,另一个部分是APP的处理。
    data_source结构体中有process函数,process函数就是数据处理的起点,在这里会干什么事情呢?它会调用各个层的处理函数,也会调用APP的处理函数。

    看一下函数usb_process_event_in,在里面会做什么事情呢?
    usb_process_event_in
      // notify uppper 通知更上面的层次
      packet_handler(HCI_EVENT_PACKET, hci_event_in_buffer, bytes_read);
      packet_handler是一个函数指针,static void (*packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size) = &usb_dummy_handler;
      该函数指针在哪里被设置呢?
      static void usb_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
        log_info("registering packet handler");
        packet_handler = handler;
      }
    在硬件相关的结构体hci_transport_usb里面,有一个注册函数usb_register_packet_handler。

    对于usb蓝牙模块,它使用hci_transport_h2_winusb.c文件中抽象出来的hci_transport_usb结构体,在这个结构体里面有usb_register_packet_handler函数,
    static void usb_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
      log_info("registering packet handler");
      packet_handler = handler; 对函数指针packet_handler进行赋值。
    }
    此处的handler是什么?就需要看看谁调用了register_packet_handler函数指针,调用了register_packet_handler函数指针,就相当于调用了usb_register_packet_handler函数
    在hci.c文件中的hci_init函数中调用了register_packet_handler函数指针。
    hci_init
      // register packet handlers with transport
      transport->register_packet_handler(&packet_handler); 从这个地方可以看出,上面的handler就是hci.c文件中的packet_handler
      再看一下参数packet_handler
      static void packet_handler(uint8_t packet_type, uint8_t *packet, uint16_t size){
        hci_dump_packet(packet_type, 1, packet, size);
        switch (packet_type) {
          case HCI_EVENT_PACKET:
            event_handler(packet, size);
            break;
          case HCI_ACL_DATA_PACKET:
            acl_handler(packet, size);
            break;
      #ifdef ENABLE_CLASSIC
          case HCI_SCO_DATA_PACKET:
            sco_handler(packet, size);
            break;
      #endif
          default:
            break;
          }
       }

    在主循环中,得到数据之后,会调用btstack_data_source中的process函数,在process函数中最终会进行packet_handler = handler这样的赋值操作。最终会调用到hci.c中的packet_handler函数。在该函数中将数据分为了三类:HCI_EVENT_PACKET、HCI_ACL_DATA_PACKET、HCI_SCO_DATA_PACKET。根据类别的不同调用不同的处理函数。

    event_handler(packet, size);
    hci_emit_event
      // dispatch to all event handlers 分发给所有的事件处理器,那么这些event handler保存在event_handlers链表中。
      btstack_linked_list_iterator_t it;
      btstack_linked_list_iterator_init(&it, &hci_stack->event_handlers);从链表中将handler一个个取出来,调用那些结构体中的callback函数来处理那些数据。
      while (btstack_linked_list_iterator_has_next(&it)){
        btstack_packet_callback_registration_t * entry = (btstack_packet_callback_registration_t*) btstack_linked_list_iterator_next(&it);
        entry->callback(HCI_EVENT_PACKET, 0, event, size);
      }

    问题:谁向hci_stack->event_handlers链表中放入handler的呢?

    从图中可以看出,上面的各个层都调用了hci_add_event_handler,上面的各层如果对数据感兴趣的话就调用hci_add_event_handler函数在链表中添加自己的处理函数。

    void hci_add_event_handler(btstack_packet_callback_registration_t * callback_handler){
      btstack_linked_list_add_tail(&hci_stack->event_handlers, (btstack_linked_item_t*) callback_handler); 向event_handler链表中添加了btstack_packet_callback_registration_t结构体,这个结构体是什么样的呢?
    }

    typedef struct {
      btstack_linked_item_t item;
      btstack_packet_handler_t callback;  有callback函数,刚好与上面对应起来。
    } btstack_packet_callback_registration_t;

    5. 谁来启动数据传输?

    hci_power_control会启动蓝牙模块,向蓝牙模块发送第一个数据,以后所有的数据都会在主循环中进行的,收到数据后将调用process函数。

  • 相关阅读:
    harbor两层nginx代理导致push不成功401
    docker hub国内镜像
    iOS越狱后导入照片
    Failed to list *v1.Secret: secrets is forbidden: User "system:node
    Centos设置limit最大打开文件数和最大进程数
    grafana设置主页面板
    MySQL数据库设计规范
    tcp time_wait
    mysqldiff No module named utilities.common.tools
    mongodb 慢查询排查
  • 原文地址:https://www.cnblogs.com/-glb/p/11617662.html
Copyright © 2011-2022 走看看