本帖最后由 bigfanofloT 于 2021-11-7 11:39 编辑

BLE-SWITCH001-GEVB开关按下后会发送广播数据包,因此想要实现RSL10-SIP-001GEVB与它通信,RSL10-SIP-001GEVB就得能够扫描到广播数据包。首先来学习学习关于BLE蓝牙广播的一些知识。
低功耗蓝牙设备通过广播信道发现其它设备,一个设备进行广播,而另一个设备进行扫描,这样进行扫描的设备就可以发现广播设备然后发起连接了。蓝牙广播的过程中有两个角色:
广播者:发送广播数据
观察者:扫描广播者
一个广播设备发送的广播可以被多个观察者设备接收到,广播是蓝牙通信中唯一的一种设备可以同时的向多个设备发送数据的通信方式。标准广播包的数据载荷是31字节,用于描述广播者和它具备的特性。 但也可以包含你想发送给其它设备的自定义数据。如果标准31字节载荷不够用,BLE也支持扩展的广播载荷(称之为扫描响应), 扫描响应载荷也是31字节,因此通过广播,蓝牙设备可以一次发送62字节的数据给周围的设备。广播最大的缺点是数据安全性,因为任何设备都能扫描到广播包。广播和扫描响应数据的格式必须满足一定的要求如图所示:
广播包结构.jpg
可以包含多个AD数据段,但是每个AD数据段必须由“Length:Data”组成,其中Length占用1个octet,Data部分占用Length个字节,所以一个AD段的长度为:Length+1。
BLE蓝牙广播总接如下:
1.广播包数据最大长度为31字节
2.数据格式为长度(1字节)类型(1字节)数据(n字节)......
其中长度=n+1无线开关BLE-SWITCH001-GEVB官网主页如下:
https://www.onsemi.cn/design/tools-software/evaluation-board/ble-switch001-gevb
我们下载它的固件:
无线开关固件.PNG
导入到安森美IDE。这个固件实现了向外发射广播包。
蓝牙设备名字定义是"ON/ZF BLE Switch"
无线开关-1.PNG

广播包的内容如下:
无线开关广播包源码.PNG
广播包更新函数Advertising_Update如下:
无线开关广播包源码2.PNG
其中有些内容是固定的,比如厂家名字、网址;有些内容是程序运行中修改的,比如电压、重启时间。
通过主循环函数可以看到,全局变量ble_env.adv_count在main_loop函数中自增后被填充到广播包中,然后发送广播包,每次上电的时候初始化为0,因此达到的效果就是:
按下一次开关产生电量后蓝牙芯片启动,ble_env.adv_count值为1了,发送广播包,如果能将它存储在掉电保存存储器中,那么可以达到记忆开关状态的效果。

无线开关主函数.PNG
打开手机蓝牙调试助手软件,自发电无线遥控开关按下一次后扫描到了蓝牙设备"ON/ZF BLE Switch"地址是60c0bf2a9590
3.jpg
广播包具体内容如下:
4.jpg
如前描述广播包最重要的部分称之为AdvData,AdvData由AD Structure组成,每个AD Structure的格式都是Length | AD Type | AD Data组成。
因此,第一个AD Structure是:02 01 06
第1个AD Structure的意义
01表示AD type为“可发现性标志”
bit 0: LE 有限发现模式。
bit 1: LE 普通发现模式。
bit 2: 不支持 BR/EDR。
bit 3: 对 Same Device Capable(Controller) 同时支持 BLE 和 BR/EDR。
bit 4: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR。
bit 5..7: 预留。

第2个AD Structure是:03 03 AA FE第2个AD Structure的意义

AD type为“16bit Service uuid列表”Complete list of 16 bit service UUIDs

如果无线开关要实现和RSL10-SIP-001GEVB开发板的通信,就需要在RSL10-SIP-001GEVB端编写广播包扫描程序了。好在安森美提供的这个例程就是中央设备扫描外围设备。
1.PNG
将它复制工程到工作空间:
2.PNG
从工程的readme_ble_central_client_scan.md文档可以知道该例程工作流程。在app.c的main函数中调用了函数
SCAN_APP_Initialize(),该函数在app_scan.c中实现,它完成串口初始化、消息处理函数注册:
void SCAN_APP_Initialize(void)
  • {
  •     /* Initialize UART, register callback function and set mode/baud rate */
  •     uart = &Driver_USART0;
  •     uart->Initialize(SCAN_UART_EventHandler);
  •     uart->PowerControl(ARM_POWER_FULL);
  •     uart->Control(ARM_USART_MODE_ASYNCHRONOUS, UART_BAUDRATE);
  •     /* Configure application message handlers */
  •     MsgHandler_Add(TASK_ID_GAPM, SCAN_MsgHandler);
  •     MsgHandler_Add(TASK_ID_GAPC, SCAN_MsgHandler);
  •     MsgHandler_Add(SCAN_UART_SCREEN_UPDATE_TIMEOUT, SCAN_MsgHandler);
  •     MsgHandler_Add(START_CONNECTION_CMD_TIMEOUT, SCAN_MsgHandler);
  •     MsgHandler_Add(APP_LED_TIMEOUT, SCAN_LED_Timeout_Handler);
  •     /* Initialize global variables */
  •     adv_report_list_size = 0;
  •     device_selection = 0;
  •     cliTimerStarted = false;
  • }
  • 复制代码
    该例程运行效果:
    ble测试.gif
    打印出扫描到的蓝牙设备,然后可选择连接的设备。

    app_scan.h定义了如下内容:
    /* Maximum number of devices in scan list */
  • #define ADV_REPORT_LIST_MAX             30
  • /* Set scan interval to 62.5ms and scan window to 50% of the interval */
  • #define SCAN_INTERVAL                   100
  • #define SCAN_WINDOW                     50
  • 复制代码
    SCAN_WINDOW 是蓝牙扫描的窗口大小,SCAN_INTERVAL 是蓝牙扫描间隔,窗口越大、扫描间隔越短越容易扫描到蓝牙广播包,但功耗会随之增加。

    app_scan.c中定义了SCAN_ScreenUpdateTimeout()函数,该函数会在串口打印蓝牙扫描结果,为了能够识别到无线遥控开关发送的广播数据,将它改造如下:
    /* ----------------------------------------------------------------------------
  • * Function      : void SCAN_ScreenUpdateTimeout(void)
  • * ----------------------------------------------------------------------------
  • * Description   : Periodically updates the information printed at the UART.
  • * Inputs        : None
  • * Outputs       : None
  • * Assumptions   : None
  • * ------------------------------------------------------------------------- */
  • void SCAN_ScreenUpdateTimeout(void)
  • {
  • #if 0
  •     /* VT100 codes for clearing the screen. Works on VT100 terminals only */
  •     /* Clear ("\033[2J") and move cursor ("\033[0;0H") */
  •     SCAN_UART_SendString("\033[2J\033[0;0H");
  •     SCAN_UART_SendString("Scanned devices:\n");
  •     for (int i = 0; i < adv_report_list_size; i++)
  •     {
  •         const uint8_t* addr = adv_report_list[i].adv_addr.addr;
  •         /* Create a UART string entry with device index, rssi, address, device name */
  •         /* Indicate which device was selected for a connection */
  •         snprintf(uart_tx_buffer, UART_TX_BUFFER_SIZE,
  •                  "[ ] %02d | %02d dBm | 0x%02x%02x%02x%02x%02x%02x | %s\n", i+1, adv_report_list[i].rssi,
  •                  addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], adv_report_list[i].data);
  •         if(i == device_selection)
  •         {
  •             uart_tx_buffer[1] = 'X';
  •         }
  •         SCAN_UART_SendString(uart_tx_buffer);
  •     }
  •     SCAN_UART_SendString("\nConnected devices:\n");
  •     for(uint8_t i = 0, j = 1; i < BLE_CONNECTION_MAX; i++)
  •     {
  •         if(GAPC_IsConnectionActive(i))
  •         {
  •             const uint8_t* addr = GAPC_GetConnectionInfo(i)->peer_addr.addr;
  •             /* Create a UART string entry with device index, rssi, address, device name */
  •             /* Indicate which device was selected for a connection */
  •             snprintf(uart_tx_buffer, UART_TX_BUFFER_SIZE,
  •                      "%02d | 0x%02x%02x%02x%02x%02x%02x \n", j++,
  •                      addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
  •             SCAN_UART_SendString(uart_tx_buffer);
  •         }
  •     }
  • #endif
  • #if 1
  •     for (int i = 0; i < adv_report_list_size; i++)
  •         {
  •             const uint8_t* addr = adv_report_list[i].adv_addr.addr;
  •             if((addr[5] == 0x60) && (addr[4] == 0xC0) && (addr[3] == 0xBF)&& (addr[2] == 0x2A)&& (addr[1] == 0x95)&& (addr[0] == 0x90))
  •             {
  •                     snprintf(uart_tx_buffer, UART_TX_BUFFER_SIZE,"[%02d dB]", adv_report_list[i].rssi);
  •                     SCAN_UART_SendString(uart_tx_buffer);
  •                     for(int k=0;k<GAP_ADV_DATA_LEN;k++)
  •                     {
  •                             snprintf(uart_tx_buffer, UART_TX_BUFFER_SIZE,"%02x ", adv_report_list[i].data[k]);
  •                             SCAN_UART_SendString(uart_tx_buffer);
  •                     }
  •                     SCAN_UART_SendString("\r\n");
  •                     break;
  •             }
  •         }
  •         adv_report_list_size = 0;
  • #endif
  • }
  • 复制代码
    判断扫描到的设备列表里面有没有无线遥控开关的地址,然后打印出它的数据即可。
    测试效果如下:
    接收的广播包.PNG
    可以看到帅选出了无线开关的广播包数据。