从零开始的nrf52832蓝牙开发--蓝牙BLE主函数分析

李肖遥 2022-05-06 22:03
    关注、星标公众号,直达精彩内容

来源:https://blog.csdn.net/solar_Lan/article/details/88964043



     工程目录中大部分文件都是官方 SDK 库中提供好了的,如果独立添加文件需要注意文件目录的设置。当然推荐大家直接用 SDK 模板来编写自己的方案,加快程序开发。

       但是真正要运行,需要大家弄清楚整个蓝牙工程的基本框架,知道哪里是干嘛的?哪里需要大家编写的?就比如现在建楼房,需要先打一个框架,然后往框架上填东西。带着上面几个问题进行下面这节的讨论。

       学过单片机的同学都知道,主函数 main 是一个程序的基本框架。而蓝牙样板工程中主函数 main 就是我们需要编写的地方,那怎么编写了?为了弄清楚这个问题,我们就必须来分析下这个模板工程的代码功能及意义,首先来看下主函数:


主函数很长,功能很多,我们画个图来直观的表示下:

下面详细分析main函数中的初始化过程:

1、外设初始化

       外设初始化实现需要使用的一些外部设备,外部设备的使用大家可以先学习外设教程的部分(购买的配套资料有提供PDF教程和工程代码)。下面主要看看协议栈下外设部分如何初始化。

1.1、log_init()函数

  1. /**@brief Function for initializing the nrf log module.

  2. */

  3. static void log_init(void)

  4. {

  5. ret_code_t err_code = NRF_LOG_INIT(NULL);

  6. APP_ERROR_CHECK(err_code);


  7. NRF_LOG_DEFAULT_BACKENDS_INIT();//初始化log设置,配置log通道

  8. }

       LOG 打印功能是为了方便调试,在设备和人之间建立一个信息交换方式。选择两个通道信息输出就行,一个是串口通道,一个是仿真器 jlink 的 RTT 通道。由于 nrf5x 芯片串口端口只有一个,当串口通道被占用,RTT 的输出成为唯一的方法。

1.2、timers_init()函数

  1. static void timers_init(void)

  2. {

  3. // Initialize timer module.

  4. ret_code_t err_code = app_timer_init();

  5. APP_ERROR_CHECK(err_code);


  6. // Create timers.


  7. /* YOUR_JOB: Create any timers to be used by the application.

  8. Below is an example of how to create a timer.

  9. For every new timer needed, increase the value of the macro APP_TIMER_MAX_TIMERS by

  10. one.

  11. ret_code_t err_code;

  12. err_code = app_timer_create(&m_app_timer_id, APP_TIMER_MODE_REPEATED, timer_timeout_handler);

  13. APP_ERROR_CHECK(err_code); */

  14. }

       创建要由应用程序使用的任何定时器,并且官方给了如何创建一个计时器的实例。后面在心电,或者接近等应用实例的时候就会被使用,设置计数器 id 。对于每一个新的计时器需要,增加宏 APP_TIMER_MAX_TIMERS。如果不使用定时器,这个可以不改变。

1.3、buttons_leds_init(&erase_bonds)函数

  1. static void buttons_leds_init(bool * p_erase_bonds)

  2. {

  3. ret_code_t err_code;

  4. bsp_event_t startup_event;


  5. err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);

  6. APP_ERROR_CHECK(err_code);


  7. err_code = bsp_btn_ble_init(NULL, &startup_event);

  8. APP_ERROR_CHECK(err_code);


  9. *p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);

  10. }

       初始化 LED 和按键的操作,这个注意,直接调用的 app 文件,也就是定义了的应用库,官方提供,大家可以具体深入源代码,如果要使用中断需要修改,后面如果遇到这方面的应用再详细说明,如果不使用端口中断,这个可以不做修改。

       这里定义了按键和 LED,下面给了一个需要回调的 bsp_event_handler,就是说这个函数初始化按键后,系统会自动帮你检测按键或者 LED 发生变化,如果有变化了,系统就回来调用。

       同时配置了一个按键控制函数 bsp_btn_ble_init,可以用按键休眠和启动蓝牙设备广播。

  1. uint32_t bsp_btn_ble_init(bsp_btn_ble_error_handler_t error_handler, bsp_event_t * p_startup_bsp_evt)

  2. {

  3. uint32_t err_code = NRF_SUCCESS;


  4. m_error_handler = error_handler;


  5. if (p_startup_bsp_evt != NULL)

  6. {

  7. startup_event_extract(p_startup_bsp_evt);

  8. }


  9. if (m_num_connections == 0)

  10. {

  11. err_code = advertising_buttons_configure();

  12. }


  13. return err_code;

  14. }

        回调的 bsp_event_handler,也就是 bsp 中断服务函数如下,实现多个事件,比如 BSP_EVENT_SLEEP 进入睡眠, BSP_EVENT_DISCONNECT 蓝牙断开事件, BSP_EVENT_WHITELIST_OFF 重新广播没有白名单事件等,对应事件执行对应操作。

  1. static void bsp_event_handler(bsp_event_t event)

  2. {

  3. ret_code_t err_code;


  4. switch (event)

  5. {

  6. case BSP_EVENT_SLEEP:

  7. sleep_mode_enter();

  8. break; // BSP_EVENT_SLEEP


  9. case BSP_EVENT_DISCONNECT:

  10. err_code = sd_ble_gap_disconnect(m_conn_handle,

  11. BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);

  12. if (err_code != NRF_ERROR_INVALID_STATE)

  13. {

  14. APP_ERROR_CHECK(err_code);

  15. }

  16. break; // BSP_EVENT_DISCONNECT


  17. case BSP_EVENT_WHITELIST_OFF:

  18. if (m_conn_handle == BLE_CONN_HANDLE_INVALID)

  19. {

  20. err_code = ble_advertising_restart_without_whitelist(&m_advertising);

  21. if (err_code != NRF_ERROR_INVALID_STATE)

  22. {

  23. APP_ERROR_CHECK(err_code);

  24. }

  25. }

  26. break; // BSP_EVENT_KEY_0


  27. default:

  28. break;

  29. }

  30. }

  2、电源管理初始化

  2.1、power_management_init()函数

        电源管理函数初始化函数,主要是实现 cortex-m4 内核 SCB 里低功耗管理设置的初始化,一般情况下可以不变动。

  1. /**@brief Function for initializing power management.

  2. */

  3. static void power_management_init(void)

  4. {

  5. ret_code_t err_code;

  6. err_code = nrf_pwr_mgmt_init();

  7. APP_ERROR_CHECK(err_code);

  8. }

  1. ret_code_t nrf_pwr_mgmt_init(void)

  2. {

  3. NRF_LOG_INFO("Init");


  4. m_shutdown_started = false;

  5. nrf_mtx_init(&m_sysoff_mtx);//初始化互斥量

  6. nrf_section_iter_init(&m_handlers_iter, &pwr_mgmt_data);//初始化迭代器的函数


  7. PWR_MGMT_SLEEP_INIT();//休眠初始化

  8. PWR_MGMT_DEBUG_PINS_INIT();//调试管脚初始化

  9. PWR_MGMT_STANDBY_TIMEOUT_INIT();//待机超时初始化

  10. PWR_MGMT_CPU_USAGE_MONITOR_INIT();//CPU使用率跟踪初始化


  11. return PWR_MGMT_TIMER_CREATE();

  12. }

3、协议栈初始化

3.1、ble_stack_init()协议初始化

协议栈初始化工作主要做下面几点:

1:协议栈回复使能应答,主要工作就是协议栈时钟初始化配置。

2:初始化协议栈,设置协议栈相关处理函数,使能协议栈。

3:注册蓝牙处理调度事件。

具体代码如下:

  1. static void ble_stack_init(void)

  2. {

  3. ret_code_t err_code;


  4. err_code = nrf_sdh_enable_request();//协议栈回复使能应答,主要是配置协议栈时钟

  5. APP_ERROR_CHECK(err_code);


  6. // Configure the BLE stack using the default settings.

  7. // Fetch the start address of the application RAM.

  8. // 配置协议栈使用默认地址,获取RAM的开始地址

  9. uint32_t ram_start = 0;

  10. //默认配置包括协议栈起始地址,连接配置等

  11. err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);

  12. APP_ERROR_CHECK(err_code);


  13. // 使能协议栈.

  14. err_code = nrf_sdh_ble_enable(&ram_start);

  15. APP_ERROR_CHECK(err_code);


  16. // 注册蓝牙处理事件.

  17. NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);

  18. }

4、GAP初始化和GATT初始化

4.1、gap_params_init()函数gap初始化

       通用访问配置文件(Generic Access Profile, GAP),该 Profile 保证不同的 Bluetooth 产品可以互相发现对方并建立连接。

(GAP)定义了蓝牙设备如何发现和建立与其他设备的安全(或不安全)连接。它处理一些一般模式的业务(如询问、命名和搜索)和一些安全性问题(如担保),同时还处理一些有关连接的业务(如链路建立、信道和连接建立)。GAP 规定的是一些一般性的运行任务。因此,它具有强制性,并作为所有其它蓝牙应用规范的基础。

GAP 是所有其他配置文件的基础,它定义了在蓝牙设备间建立基带链路的通用方法。除此之外,GAP 还定义了下列内容:

①必须在所有蓝牙设备中实施的功能;

②发现和链接设备的通用步骤;

③基本用户界面术语。

       在 GAP 初始化里实际上只做了两个工作,一个是配置设备名称,或者生成设备图标。第二个功能就是配置 GAP 的连接参数,代码如下所.

  1. static void gap_params_init(void)

  2. {

  3. ret_code_t err_code;

  4. ble_gap_conn_params_t gap_conn_params;

  5. ble_gap_conn_sec_mode_t sec_mode;


  6. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);//设置设备名称


  7. err_code = sd_ble_gap_device_name_set(&sec_mode,

  8. (const uint8_t *)DEVICE_NAME,

  9. strlen(DEVICE_NAME));

  10. APP_ERROR_CHECK(err_code);


  11. /* YOUR_JOB: Use an appearance value matching the application's use case.

  12. err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_);

  13. APP_ERROR_CHECK(err_code); */


  14. memset(&gap_conn_params, 0, sizeof(gap_conn_params));

  15. //初始化GAP连接间隔,从机延迟,超时时间

  16. gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;

  17. gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;

  18. gap_conn_params.slave_latency = SLAVE_LATENCY;

  19. gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;

  20. //把配置的参数设置成功

  21. err_code = sd_ble_gap_ppcp_set(&gap_conn_params);

  22. APP_ERROR_CHECK(err_code);

  23. }

4.2、gatt_init()函数

 

       GATT 称为通用属性规范 Generic Attribute profile,GATT 层是传输真正数据所在的层。包括了一个数据传输和存储框架以及其基本操作。其大部分设置是在服务中进行的,在主函数中只需要初始化数据长度这个参数,代码如下所示,gatt_init 函数中调用了 nrf_ble_gatt_init 函数,该函数中定义了 gatt 中的主机,从机的最大 MTU 的长度,以及协商数据的长度。

  1. /**@brief Function for initializing the GATT module.

  2. */

  3. static void gatt_init(void)

  4. {

  5. ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);

  6. APP_ERROR_CHECK(err_code);

  7. }


  8. ret_code_t nrf_ble_gatt_init(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_handler_t evt_handler)

  9. {

  10. VERIFY_PARAM_NOT_NULL(p_gatt);


  11. p_gatt->evt_handler = evt_handler;

  12. p_gatt->att_mtu_desired_periph = NRF_SDH_BLE_GATT_MAX_MTU_SIZE;

  13. p_gatt->att_mtu_desired_central = NRF_SDH_BLE_GATT_MAX_MTU_SIZE;

  14. p_gatt->data_length = NRF_SDH_BLE_GAP_DATA_LENGTH;


  15. for (uint32_t i = 0; i < NRF_BLE_GATT_LINK_COUNT; i++)

  16. {

  17. link_init(&p_gatt->links[i]);

  18. }


  19. return NRF_SUCCESS;

  20. }

5、广播初始化

5.1、static void advertising_init(void):函数初始化广播功能

  1. /**@brief Function for initializing the Advertising functionality.

  2. */

  3. static void advertising_init(void)

  4. {

  5. ret_code_t err_code;

  6. ble_advertising_init_t init;


  7. memset(&init, 0, sizeof(init));


  8. init.advdata.name_type = BLE_ADVDATA_FULL_NAME;//广播时的名称显示

  9. init.advdata.include_appearance = true;//是否需要图标

  10. //蓝牙设备模式

  11. init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

  12. //UUID

  13. init.advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);

  14. init.advdata.uuids_complete.p_uuids = m_adv_uuids;


  15. init.config.ble_adv_fast_enabled = true;//广播类型

  16. init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;//广播间隔

  17. init.config.ble_adv_fast_timeout = APP_ADV_DURATION;//广播超时


  18. init.evt_handler = on_adv_evt;


  19. err_code = ble_advertising_init(&m_advertising, &init);//初始化广播,导入参数

  20. APP_ERROR_CHECK(err_code);


  21. ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);//设置广播识别号

  22. }

       广播初始化实际上就是初始化两个结构体,一个是&advdata 广播数据,一个是&config 选择项广播数据结构体如下,列出了一些需要初始化的广播数据:

  1. /**@brief Advertising data structure. This structure contains all options and data needed for encoding and

  2. * setting the advertising data. */

  3. typedef struct

  4. {

  5. ble_advdata_name_type_t name_type; /**< Type of device name. */

  6. uint8_t short_name_len; /**< Length of short device name (if short type is specified). */

  7. bool include_appearance; /**< Determines if Appearance shall be included. */

  8. uint8_t flags; /**< Advertising data Flags field. */

  9. int8_t * p_tx_power_level; /**< TX Power Level field. */

  10. ble_advdata_uuid_list_t uuids_more_available; /**< List of UUIDs in the 'More Available' list. */

  11. ble_advdata_uuid_list_t uuids_complete; /**< List of UUIDs in the 'Complete' list. */

  12. ble_advdata_uuid_list_t uuids_solicited; /**< List of solicited UUIDs. */

  13. ble_advdata_conn_int_t * p_slave_conn_int; /**< Slave Connection Interval Range. */

  14. ble_advdata_manuf_data_t * p_manuf_specific_data; /**< Manufacturer specific data. */

  15. ble_advdata_service_data_t * p_service_data_array; /**< Array of Service data structures. */

  16. uint8_t service_data_count; /**< Number of Service data structures. */

  17. bool include_ble_device_addr; /**< Determines if LE Bluetooth Device Address shall be included. */

  18. ble_advdata_le_role_t le_role; /**< LE Role field. Included when different from @ref BLE_ADVDATA_ROLE_NOT_PRESENT. @warning This field can be used only for NFC. For BLE advertising


  19. } ble_advdata_t;

       一个广播数据实际上最多可以携带 31 字节的数据,它通常包含用户可读的名字、关于设备发送数据包的有关信息、用于表示此设备是否可被发现的标志等类似的标志,如上面结构体的定义。

       当主机接收到广播包后,它可能发送请求更多数据包的请求,称为扫描回应,如果它被设置成主动扫描,从机设备将会发送一个扫描回应做为对主机请求的回应,扫描回应最多也可以携带31字节的数据。广播扫描回应包的数据结构类型可以和广播包一致,也如上面结构体定义。那么如何设置广播包了

6、服务初始化

6.1、services_init():服务初始化

  1. static void services_init(void)

  2. {

  3. ret_code_t err_code;

  4. nrf_ble_qwr_init_t qwr_init = {0};


  5. // Initialize Queued Write Module.

  6. qwr_init.error_handler = nrf_qwr_error_handler;


  7. err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);

  8. APP_ERROR_CHECK(err_code);


  9. /* YOUR_JOB: Add code to initialize the services used by the application.

  10. ble_xxs_init_t xxs_init;

  11. ble_yys_init_t yys_init;

  12. // Initialize XXX Service.

  13. memset(&xxs_init, 0, sizeof(xxs_init));

  14. xxs_init.evt_handler = NULL;

  15. xxs_init.is_xxx_notify_supported = true;

  16. xxs_init.ble_xx_initial_value.level = 100;

  17. err_code = ble_bas_init(&m_xxs, &xxs_init);

  18. APP_ERROR_CHECK(err_code);

  19. // Initialize YYY Service.

  20. memset(&yys_init, 0, sizeof(yys_init));

  21. yys_init.evt_handler = on_yys_evt;

  22. yys_init.ble_yy_initial_value.counter = 0;

  23. err_code = ble_yy_service_init(&yys_init, &yy_init);

  24. APP_ERROR_CHECK(err_code);

  25. */

  26. }

       服务初始化就是建立一个服务声明,给一个 RAM 空间,专门对服务进行初始化和声明。这个代码在样例里是空的,也就是说蓝牙样例里只搭了一个框架,而没有建立蓝牙服务,那么在后面的应用实例教程里会来讲解如何添加服务。具体实例请看后面的蓝牙服务篇教程。

7、连接参数和安全参数初始化

7.1、conn_params_init():连接参数初始化

  1. /**@brief Function for initializing the Connection Parameters module.

  2. */

  3. static void conn_params_init(void)

  4. {

  5. ret_code_t err_code;

  6. ble_conn_params_init_t cp_init;


  7. memset(&cp_init, 0, sizeof(cp_init));


  8. cp_init.p_conn_params = NULL;

  9. cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;

  10. cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;

  11. cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;

  12. cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;

  13. cp_init.disconnect_on_fail = false;

  14. cp_init.evt_handler = on_conn_params_evt;

  15. cp_init.error_handler = conn_params_error_handler;


  16. err_code = ble_conn_params_init(&cp_init);

  17. APP_ERROR_CHECK(err_code);

  18. }

       SDK 提供了一个名为 ble_conn_params 的模块用于管理连接参数更新,它通过 SoftDevice API 进行处理,包括请求的时间和第一次请求被拒绝再发送一个新的请求。SoftDevice API 函数是经过封装后的函数,无法查看源函数,大家只要通过帮助文档查找函数意义,所有带 sd 前缀的函数名就是SoftDevice API 函数。

       在初始化结构体 ble_conn_params_init_t 中,定义了更新过程的有关参数,例如,是否开始连接,什么情况开始写入一个特定的 CCCD,是否使用连接参数,发送更新请求的延时等等。大家可以查看源码,在 BLE_CONN_PARAMS.H 文件内。

       在初始化函数 ble_conn_params_init()中,使用封装了初始化连接参数(ble_gap_conn_params_t)的结构体 ble_conn_params_init_t 作为输入参数进行连接参数初始化结构体。

       ble_conn_params 在 SDK 模块确保与主机(集中器)的连接参数相适应,如果不适应,外围设备将要求更改连接参数,超过设定的更新次数都没有更新成功后,它就会断开连接或者根据设置返回一个事件到应用层。

8、设备管理初始化

  1. /**@brief Function for the Peer Manager initialization.

  2. */

  3. static void peer_manager_init(void)

  4. {

  5. ble_gap_sec_params_t sec_param;

  6. ret_code_t err_code;


  7. err_code = pm_init();

  8. APP_ERROR_CHECK(err_code);


  9. memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));


  10. // Security parameters to be used for all security procedures.

  11. sec_param.bond = SEC_PARAM_BOND;

  12. sec_param.mitm = SEC_PARAM_MITM;

  13. sec_param.lesc = SEC_PARAM_LESC;

  14. sec_param.keypress = SEC_PARAM_KEYPRESS;

  15. sec_param.io_caps = SEC_PARAM_IO_CAPABILITIES;

  16. sec_param.oob = SEC_PARAM_OOB;

  17. sec_param.min_key_size = SEC_PARAM_MIN_KEY_SIZE;

  18. sec_param.max_key_size = SEC_PARAM_MAX_KEY_SIZE;

  19. sec_param.kdist_own.enc = 1;

  20. sec_param.kdist_own.id = 1;

  21. sec_param.kdist_peer.enc = 1;

  22. sec_param.kdist_peer.id = 1;


  23. err_code = pm_sec_params_set(&sec_param);

  24. APP_ERROR_CHECK(err_code);


  25. err_code = pm_register(pm_evt_handler);

  26. APP_ERROR_CHECK(err_code);

  27. }

9、广播开始

advertising_start(erase_bonds);

  1. /**@brief Function for starting advertising.

  2. */

  3. static void advertising_start(bool erase_bonds)

  4. {

  5. if (erase_bonds == true)

  6. {

  7. delete_bonds();

  8. // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event

  9. }

  10. else

  11. {

  12. ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);


  13. APP_ERROR_CHECK(err_code);

  14. }

  15. }

       广播数据的设置在 main.c 的 advertising_start 中,安全广播初始化中设置的广播数据结构开始广播,主函数中,首先启动的为快速广播。

10、电源待机

idle_state_handle();电源待机

       主函数中,最后一个循环等待,调用了 idle_state_handle()函数,这个函数字面意思为无效状态操作,也就是说在没有蓝牙事件或者芯片处理事件的时候,设备调用这个函数,可以使设备处于 system on 状态,也就是待机状态。idle_state_handle()函数中调用了 nrf_pwr_mgmt_run 函数中,这个函数中使用了 SDK 提供了一个协议栈函数名为 sd_app_evt_wait 的模块用于管理电源,可以直接调用,当中断事件发生后,设备被唤醒工作。

  1. /**@brief Function for handling the idle state (main loop).

  2. *

  3. * @details If there is no pending log operation, then sleep until next the next event occurs.

  4. */

  5. static void idle_state_handle(void)

  6. {

  7. if (NRF_LOG_PROCESS() == false)

  8. {

  9. nrf_pwr_mgmt_run();

  10. }

  11. }

11、 下载验证

        之前下载的都是外设案例,外设代码不带协议栈,从本章后讲解的都将上蓝牙程序,蓝牙程序带协议栈,下载过程和外设有区别,下面详解进行介绍,后面的蓝牙程序下载方法类似。

1、首先使用 nrfgo 下载协议栈,打开协议栈下载软件 nrfgo,如下图所示,如果仿真器没有连接,nRF5x Programming 显示为灰色:

  1. 按照《下载错误解决办法里的方法》文档里的方法,把仿真器驱动安装好后,把仿真器和开发连接上,会如下图所示:

3 选择 Program Application ,点击 Browse 选择添加你要下载的协议版本,然后点击 Program 进行下载:

4.下载完协议栈,然后再用 keil 下载工程项目,工程项目目录如下图所示:

5. 安装手机 APP nrf connect,或者应用商店直接下载,安装后打开手机 APP nrf connect 观察如下现象,发现广播名称为 Nordic_Template 的广播信号:

6.点击设备,显示连接成功,LED1 灯熄灭,LED2 灯点亮,查看设备属性,观察相关参数:

蓝牙技术联盟所用的基本 UUID 为 16bit UUID,本例中蓝牙服务为空,我们观察到的是 GAP 基础 UUID 和 GATT 基础 UUID,再搭建一个空服务中,基础 GAP 和 GATT。

UUID 是底层自带的,在文件 ble_types.h 文件中,对这个 GATT specific UUID 和

GAP specific UUID 有列表出来,如下图所示:

在 APP 连接广播后,服务中第一部分就是 GAP specific UUID 以及其的特征值,第二部分就是 GATT specific UUID 以及其的特征值,对比 APP 连接参数和上图的定义类表。

免责声明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 150浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 50浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 128浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 80浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 82浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 48浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 115浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 100浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 37浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 126浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 37浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 33浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 37浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 92浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 211浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦