欧美自拍日韩主播在线观看_国产后入清纯学生妹人狗_无码a级毛片免费内谢_亚洲少妇免费在线视频_aⅴ黄色成人网站_免费高清特级毛片_爱豆传媒国产剧情_性欧美xxx在线观看_中文字幕精品乱码亚洲一区_91电影久久国产

當(dāng)前位置:首頁 > 服務(wù)支持 > 博客

從零開始手把手教你寫一個基于nRF54L15的BLE工程作者:迅通科技    時間:2024-12-30

最近收到了Nordic最新的旗艦級BLE芯片 nRF54L15的開發(fā)板,大概研究了一下,今天教大家怎么在上面把BLE跑起來。

前言:開發(fā)板概覽

 1、打開包裝之后里面是用防靜電袋包裝的開發(fā)板和NFC天線,nRF54L15DK的代號是PCA10156,但是nRF54L15的開發(fā)板對比nRF52832的開發(fā)板來看,nRF54L15的開發(fā)板沒有再采用Arduion接口將IO引出。開發(fā)板上用到的元器件也少了很多,看下來只用到五顆主芯片。


 開發(fā)板的JLINK芯片用的是Nordic的nRF5340,JLINK的USB口換成了Typc-C口。


 電源芯片用上了Nordic自己的PMIC nPM1300


 最后來看一下nRF54L15部分,這里外掛了一個MXIC 64Mbit的flash,nRF54L15最小系統(tǒng)的元器件也少了很多。


在網(wǎng)上可以找到nRF54L15的資料:

介紹:https://www.nordicsemi.com/Products/nRF54L15
規(guī)格書:https://docs-be.nordicsemi.com/bundle/ps_nrf54L15/attach/pdf/nRF54L15_nRF54L10_nRF54L05_Datasheet_v0.7.pdf
硬件設(shè)計:https://www.nordicsemi.com/-/media/Software-and-other-downloads/Reference-Layouts/nRF54L15/nRF54L15-QFAA-Reference-Layout-0_7.zip

 2、環(huán)境搭建

根據(jù)我原廠的朋友的說法,新的nRF54L15需要用到NRF Connect SDK去開發(fā),

這里環(huán)境搭建可以參考官方的文檔:Installing the nRF Connect SDK (nordicsemi.com)

最新的文檔已經(jīng)推薦使用nrfutil來下載SDK了,但是我個人還是更喜歡用west的方式

這里可以參考中國區(qū)原廠FAE的博客:開發(fā)你的第一個nRF Connect SDK(NCS)/Zephyr應(yīng)用程序 - iini - 博客園 (cnblogs.com)

按照博客中的方法安裝好Choco和相關(guān)的工具之后,直接執(zhí)行下面兩條命令即可:

west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.9.0

west update
nRF54系列的SDK要使用NCS 2.7.0及以上的版本,下載SDK的時候需要注意。

NRF CONNECT SDK現(xiàn)在的編譯環(huán)境也換成了VS Code + Nordic插件的方式,這里的編譯環(huán)境搭建也可以參考上面中國區(qū)原廠FAE的博客。


一、在nRF54L15D上運行你的第一個程序
1、搭建好環(huán)境之后,首先我們從SDK中Copy一個Hello World的工程


2、編譯在NRF54L15DK上運行,這里需要注意NCS2.7.x及以上的版本在編譯的時候需要選擇SysBuild


3、程序編譯之后下載到nRF54L15DK中,打開串口我們可以看到串口打印如下:

分別是NCS和Zephyr的版本,以及Hello World!和Boards的型號


 4、我們打開這個工程的main.c,可以看到這個工程非常簡單,就是調(diào)用了printf來打印Hello World!和CONFIG_BOARD_TARGET這個宏


二、添加LOG模塊
上面的Hello World使用了printf來打印,在我們的程序里面可以調(diào)用LOG模塊來打印調(diào)試日志,這樣方便我們后續(xù)在產(chǎn)品量產(chǎn)的時候關(guān)閉LOG模塊
1、這里我們先在hello_world/prj.conf中下面這些的宏,開啟LOG
復(fù)制代碼
#Config logger
CONFIG_LOG=y
CONFIG_USE_SEGGER_RTT=n
CONFIG_LOG_BACKEND_RTT=n
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_DEFAULT_LEVEL=3
復(fù)制代碼
2、在main.c中加入下面這些頭文件,調(diào)用LOG的庫,并注冊LOG模組
復(fù)制代碼
//Add Log Library file
#include <zephyr.h>
#include <zephyr/logging/log.h>

//Register LOG Module
#define LOG_MODULE_NAME ble_hello_world
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
復(fù)制代碼
3、這里我們提前寫一個bluetooth_init的函數(shù),并在bluetooth_init中加入一些LOG信息

復(fù)制代碼
int bluetooth_init(void)
{
    int err_code;

    LOG_INF("Initiallzing BLE");

    err_code = 0;

    return err_code;
}
復(fù)制代碼
4、、在main函數(shù)中調(diào)用函數(shù) bluetooth_init

復(fù)制代碼
int main(void)
{
    int err_code;

    err_code = bluetooth_init();
    if(err_code){
        LOG_ERR("Bluetooth_init returnrd %d", err_code)
    }
    printf("Hello World! %s\n", CONFIG_BOARD_TARGET);
}
復(fù)制代碼
5、我們來編譯并下載程序到nRF54L15DK中會看到程序進(jìn)入了bluetooth_init 并在串口打印了LOG:


三、使能協(xié)議棧

1、接下來,我們需要加入BLE相關(guān)的頭文件

//Add BLE Library file
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
2、prj.conf中加入這些宏,開啟BLE相關(guān)的宏

復(fù)制代碼
#Config BLE
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="BLE_Hello_World"
CONFIG_BT_DEVICE_APPEARANCE=0
CONFIG_BT_LL_SOFTDEVICE=y
CONFIG_BT_MAX_CONN=1

CONFIG_ASSERT=y
復(fù)制代碼
3、在 bluetooth_init中調(diào)用bt_enable,這是一個庫函數(shù),這個函數(shù)的定義的bluetooth.h中,因為bt_enable只負(fù)責(zé)調(diào)度BLE協(xié)議棧的啟動,而在執(zhí)行其他函數(shù)之前需要確保協(xié)議棧完成了啟動,所以我們要做一個簡單判斷

復(fù)制代碼
int bluetooth_init(void)
{
    int err_code;

    err_code = bt_enable(bt_ready_callback);
    if(err_code)
    {
        LOG_ERR("BLE Enable returned %d",err_code);
     return err_code;
    }

    LOG_INF("Initiallzing BLE");
    return err_code;
}
復(fù)制代碼
3、我們?nèi)タ匆幌逻@個函數(shù)的用法,這里看到bt_enable需要一個回調(diào)函數(shù)

復(fù)制代碼
/**
 * @brief Enable Bluetooth
 *
 * Enable Bluetooth. Must be the called before any calls that
 * require communication with the local Bluetooth hardware.
 *
 * When @kconfig{CONFIG_BT_SETTINGS} is enabled, the application must load the
 * Bluetooth settings after this API call successfully completes before
 * Bluetooth APIs can be used. Loading the settings before calling this function
 * is insufficient. Bluetooth settings can be loaded with settings_load() or
 * settings_load_subtree() with argument "bt". The latter selectively loads only
 * Bluetooth settings and is recommended if settings_load() has been called
 * earlier.
 *
 * @param cb Callback to notify completion or NULL to perform the
 * enabling synchronously. The callback is called from the system workqueue.
 *
 * @return Zero on success or (negative) error code otherwise.
 */
int bt_enable(bt_ready_cb_t cb);
復(fù)制代碼
我們再去看bt_ready_cb_t這個參數(shù)的定義,從這個注釋中可以得知這個回調(diào)的作用是通知BLE已使能

復(fù)制代碼
/**
 * @typedef bt_ready_cb_t
 * @brief Callback for notifying that Bluetooth has been enabled.
 *
 * @param err zero on success or (negative) error code otherwise.
 */
typedef void (*bt_ready_cb_t)(int err);
復(fù)制代碼
所以為了確保BLE協(xié)議棧已經(jīng)完成了使能,我們需要自己寫一個回調(diào)函數(shù)

復(fù)制代碼
/*BLE Enable Callback function*/
void bt_ready_callback(int err_code)
{
    if(err_code)
    {
        LOG_ERR("BLE Enable callback returned %d",err_code);
    }
}
復(fù)制代碼
4、這里我們在bluetooth_init中添加一個信號量,等待bt_ready_cb_t通知協(xié)議棧已使能,這里我們調(diào)用K_SEM_DEFINE初始化一個信號量,并將其計數(shù)設(shè)為 1、界限設(shè)為 1,然后在bluetooth_init中調(diào)用k_sem_take()獲取這個信號量,因為我們將計數(shù)設(shè)置為了1,所以程序會一直在bt_enable()這里等待協(xié)議棧使能完成,當(dāng)然可以將K_FOREVER設(shè)置為一個時間,超時之后程序會繼續(xù)往下執(zhí)行。

復(fù)制代碼
static K_SEM_DEFINE(ble_init_ok, 1, 1);

int bluetooth_init(void)
{
    int err_code;
   
   LOG_INF("Initiallzing BLE");

    err_code = bt_enable(bt_ready_callback);
    if(err_code)
    {
        LOG_ERR("BLE Enable returned %d",err_code);
     return err_code;
    }
  k_sem_take(&ble_init_ok, K_FOREVER);  return err_code;
}
復(fù)制代碼
因為這里我們要確保BLE協(xié)議棧使能完成,所以我們可以在bt_ready_callback()中釋放這個信號量,這樣我們可以通過信號量的同步得知BLE協(xié)議棧已經(jīng)被使能。

復(fù)制代碼
/*BLE Enable Callback function*/
void bt_ready_callback(int err_code)
{
    if(err_code)
    {
        LOG_ERR("BLE Enable callback returned %d",err_code);
    }
  k_sem_give(&ble_init_ok);
}
復(fù)制代碼
5、最后我們編譯看一下有什么效果,這里可以看到BLE協(xié)議棧已經(jīng)被使能,并打印了版本信息等LOG


 四、開啟廣播

使能BLE協(xié)議棧之后,我們接下來要做的就是開啟一個BLE廣播

1、我們來設(shè)置廣播包的內(nèi)容,我們可以在廣播包中加入UUID,這里我們可以寫一個宏來定義UUID的號碼

/** @brief UUID of the Hello World Service. **/
#define BT_UUID_HOWD_VAL \
    BT_UUID_128_ENCODE(0x6e400001, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e)
#define BT_UUID_HOWD_SERVICE   BT_UUID_DECLARE_128(BT_UUID_HOWD_VAL)
2、我們寫一個宏來設(shè)置廣播名稱,這個宏最終調(diào)用的是在prj.conf中CONFIG_BT_DEVICE_NAME的值,這里我的定義是CONFIG_BT_DEVICE_NAME="BLE_Hello_World"

/*Config BLE Device Name*/
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN    (sizeof(DEVICE_NAME) - 1)
3、最后我們把上面的UUID和名稱放在廣播包數(shù)據(jù)中,這里我把UUID放在了Scan response里面

復(fù)制代碼
/*Config BLE Advertising Data*/
static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};
/*Config BLE Scan repsponse Data*/
static const struct bt_data srd[] = {
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_HOWD_VAL),
};
復(fù)制代碼
4、完成廣播數(shù)據(jù)定義之后,我們就可以在bluetooth_init()中調(diào)用bt_le_adv_start()來開啟廣播了。

復(fù)制代碼
int bluetooth_init(void)
{
    int err_code;
    
    LOG_INF("Initiallzing BLE");

    bt_conn_cb_register();

    err_code = bt_enable(bt_ready_callback);
    if(err_code)
    {
        LOG_ERR("BLE Enable returned %d",err_code);
        return err_code;
    }
    k_sem_take(&ble_init_ok, K_FOREVER);

    err_code = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), srd,
                  ARRAY_SIZE(srd));
    if (err_code) {
        LOG_ERR("Advertising failed to start (err_code %d)", err_code);
        return 0;
    }

    return err_code;
}
復(fù)制代碼
這里我們可以去看一下bt_le_adv_start()這個函數(shù)的用法,這是一個庫函數(shù),可以在bluetooth.h中找到聲明,可以看到這個函數(shù)一共有5個入?yún)?,分別是

1)const struct bt_le_adv_param *param,是包括廣播周期、廣播類型等參數(shù)

2)const struct bt_data *ad, size_t ad_len,分別是廣播包數(shù)據(jù)和廣播包的長度

3)const struct bt_data *sd, size_t sd_len,分別是掃描響應(yīng)包數(shù)據(jù)和掃描響應(yīng)包的長度

/**
* @brief Start advertising
 *
 * Set advertisement data, scan response data, advertisement parameters
* and start advertising.
*
* When the advertisement parameter peer address has been set the advertising
 * will be directed to the peer. In this case advertisement data and scan
* response data parameters are ignored. If the mode is high duty cycle
10 * the timeout will be @ref BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT.
11 *
12 * This function cannot be used with @ref BT_LE_ADV_OPT_EXT_ADV in the @p param.options.
13 * For extended advertising, the bt_le_ext_adv_* functions must be used.
14 *
15 * @param param Advertising parameters.
16  * @param ad Data to be used in advertisement packets.
17  * @param ad_len Number of elements in ad
18  * @param sd Data to be used in scan response packets.
19 * @param sd_len Number of elements in sd
20  *
21  * @return Zero on success or (negative) error code otherwise.
22 * @return -ENOMEM No free connection objects available for connectable
23  *                 advertiser.
24  * @return -ECONNREFUSED When connectable advertising is requested and there
25 *                       is already maximum number of connections established
26  *                       in the controller.
27  *                       This error code is only guaranteed when using Zephyr
28  *                       controller, for other controllers code returned in
29  *                       this case may be -EIO.
30  */
31 int bt_le_adv_start(const struct bt_le_adv_param *param,
32  const struct bt_data *ad, size_t ad_len,
33   const struct bt_data *sd, size_t sd_len);
在上面的代碼中,廣播參數(shù)也調(diào)用了一個現(xiàn)成的宏,這個宏在bluetooth.h中可以找到,在這個宏里面可以看到參數(shù)主義是定義了廣播類型為可連接廣播,快速廣播的最小周期和最大周期。此外廣播包中還定義了一下flag等,感興趣的讀者可以自行去看下面這幾個宏

BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)
#define BT_LE_ADV_CONN BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE, \
                       BT_GAP_ADV_FAST_INT_MIN_2, \
                       BT_GAP_ADV_FAST_INT_MAX_2, NULL)
復(fù)制代碼
#define BT_GAP_ADV_FAST_INT_MIN_2               0x00a0  /* 100 ms   */
#define BT_GAP_ADV_FAST_INT_MAX_2               0x00f0  /* 150 ms   */
    /**
     * @brief Advertise as connectable.
     *
     * Advertise as connectable. If not connectable then the type of
     * advertising is determined by providing scan response data.
     * The advertiser address is determined by the type of advertising
     * and/or enabling privacy @kconfig{CONFIG_BT_PRIVACY}.
     */
    BT_LE_ADV_OPT_CONNECTABLE = BIT(0),
復(fù)制代碼
此外bluetooth.h中除了BT_LE_ADV_CONN BT_LE_ADV_PARAM之外,還有其他好多個廣播參數(shù),感興趣的讀者可以去看看其他的宏分別定義了一些什么。

5、最后我們編譯工程來看一下有什么效果,我們可以使用nRF Connect app去查看一下廣播數(shù)據(jù)



 6、練習(xí):如果你熟悉nRF5 SDK,你會發(fā)現(xiàn)在NCS上定義個廣播參數(shù)和廣播數(shù)據(jù)會比nRF5 SDK上要簡單,不需要去寫參數(shù)眾多的指針變量,甚至可以直接用現(xiàn)成定義好的宏,許多參數(shù)甚至可以直接在prj.conf中定義即可,這大大減少了代碼開發(fā)的工作量。這里感興趣的讀者可以去自行修改Appearance,這個參數(shù)可以通過下面這個宏來定義,修改這個宏的值然后在nRF Connect app的廣播設(shè)備的圖標(biāo)會出現(xiàn)變化,SIG定義了一些標(biāo)準(zhǔn)具體的值,例如心率設(shè)備、HID設(shè)備等,具體去SIG這個網(wǎng)站查詢:https://www.bluetooth.com/specifications/assigned-numbers

CONFIG_BT_DEVICE_APPEARANCE=0
 
五、BLE的連接回調(diào)

在nRF5 SDK中BLE初始化的時候會注冊一個回調(diào)函數(shù)ble_evt_handler來處理BLE連接、斷開連接、更新PHY、Timeout等時候的中斷事件

復(fù)制代碼
/*nRF5 SDK中的BLE中斷事件*/
/**@brief Function for handling BLE events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 * @param[in]   p_context   Unused.
 */
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            NRF_LOG_INFO("Connected");
            err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
            APP_ERROR_CHECK(err_code);
            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GAP_EVT_DISCONNECTED:
            NRF_LOG_INFO("Disconnected");
            // LED indication will be changed when advertising starts.
            m_conn_handle = BLE_CONN_HANDLE_INVALID;
            break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            NRF_LOG_DEBUG("PHY update request.");
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
            // Pairing not supported
            err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_SYS_ATTR_MISSING:
            // No system attributes have been stored.
            err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTC_EVT_TIMEOUT:
            // Disconnect on GATT Client timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_TIMEOUT:
            // Disconnect on GATT Server timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        default:
            // No implementation needed.
            break;
    }
}
復(fù)制代碼
在NCS中,我們也可以這樣來做,這個章節(jié)我們將演示如何在連接和斷開連接的時候,打印LOG和點亮\熄滅DK的LED1。

 1、首先我們在bluetooth_init()加入一個回調(diào)注冊函數(shù)bt_conn_cb_register(),這個函數(shù)在conn.h,我們來看一下這個函數(shù)的聲明,可以看到這個函數(shù)的主要作用就是注冊一個callback用來在連接狀態(tài)下監(jiān)控相關(guān)事件。

復(fù)制代碼
/** @brief Register connection callbacks.
 *
 *  Register callbacks to monitor the state of connections.
 *
 *  @param cb Callback struct. Must point to memory that remains valid.
 *
 * @retval 0 Success.
 * @retval -EEXIST if @p cb was already registered.
 */
int bt_conn_cb_register(struct bt_conn_cb *cb);
復(fù)制代碼
從注釋中我們可以看到這個函數(shù)需要輸入 struct bt_conn_cb *cb 這個結(jié)構(gòu)體作為入?yún)?,這個結(jié)構(gòu)體的聲明也在conn.h中,我們?nèi)タ匆幌逻@個結(jié)構(gòu)體的注釋,可以看到這個結(jié)構(gòu)體中給出的相關(guān)BLE回調(diào)函數(shù)是和nRF5 SDK中給出的case是相似的,感興趣的讀者可以自行查看注釋來進(jìn)一步做對比。

復(fù)制代碼
/** @brief Connection callback structure.
 *
 *  This structure is used for tracking the state of a connection.
 *  It is registered with the help of the bt_conn_cb_register() API.
 *  It's permissible to register multiple instances of this @ref bt_conn_cb
 *  type, in case different modules of an application are interested in
 *  tracking the connection state. If a callback is not of interest for
 *  an instance, it may be set to NULL and will as a consequence not be
 *  used for that instance.
 */
struct bt_conn_cb {
    /** @brief A new connection has been established.
     *
     *  This callback notifies the application of a new connection.
     *  In case the err parameter is non-zero it means that the
     *  connection establishment failed.
     *
     *  @note If the connection was established from an advertising set then
     *        the advertising set cannot be restarted directly from this
     *        callback. Instead use the connected callback of the
     *        advertising set.
     *
     *  @param conn New connection object.
     *  @param err HCI error. Zero for success, non-zero otherwise.
     *
     *  @p err can mean either of the following:
     *  - @ref BT_HCI_ERR_UNKNOWN_CONN_ID Creating the connection started by
     *    @ref bt_conn_le_create was canceled either by the user through
     *    @ref bt_conn_disconnect or by the timeout in the host through
     *    @ref bt_conn_le_create_param timeout parameter, which defaults to
     *    @kconfig{CONFIG_BT_CREATE_CONN_TIMEOUT} seconds.
     *  - @p BT_HCI_ERR_ADV_TIMEOUT High duty cycle directed connectable
     *    advertiser started by @ref bt_le_adv_start failed to be connected
     *    within the timeout.
     */
    void (*connected)(struct bt_conn *conn, uint8_t err);

    /** @brief A connection has been disconnected.
     *
     *  This callback notifies the application that a connection
     *  has been disconnected.
     *
     *  When this callback is called the stack still has one reference to
     *  the connection object. If the application in this callback tries to
     *  start either a connectable advertiser or create a new connection
     *  this might fail because there are no free connection objects
     *  available.
     *  To avoid this issue it is recommended to either start connectable
     *  advertise or create a new connection using @ref k_work_submit or
     *  increase @kconfig{CONFIG_BT_MAX_CONN}.
     *
     *  @param conn Connection object.
     *  @param reason BT_HCI_ERR_* reason for the disconnection.
     */
    void (*disconnected)(struct bt_conn *conn, uint8_t reason);

    /** @brief A connection object has been returned to the pool.
     *
     * This callback notifies the application that it might be able to
     * allocate a connection object. No guarantee, first come, first serve.
     *
     * Use this to e.g. re-start connectable advertising or scanning.
     *
     * Treat this callback as an ISR, as it originates from
     * @ref bt_conn_unref which is used by the BT stack. Making
     * Bluetooth API calls in this context is error-prone and strongly
     * discouraged.
     */
    void (*recycled)(void);

    /** @brief LE connection parameter update request.
     *
     *  This callback notifies the application that a remote device
     *  is requesting to update the connection parameters. The
     *  application accepts the parameters by returning true, or
     *  rejects them by returning false. Before accepting, the
     *  application may also adjust the parameters to better suit
     *  its needs.
     *
     *  It is recommended for an application to have just one of these
     *  callbacks for simplicity. However, if an application registers
     *  multiple it needs to manage the potentially different
     *  requirements for each callback. Each callback gets the
     *  parameters as returned by previous callbacks, i.e. they are not
     *  necessarily the same ones as the remote originally sent.
     *
     *  If the application does not have this callback then the default
     *  is to accept the parameters.
     *
     *  @param conn Connection object.
     *  @param param Proposed connection parameters.
     *
     *  @return true to accept the parameters, or false to reject them.
     */
    bool (*le_param_req)(struct bt_conn *conn,
                 struct bt_le_conn_param *param);

    /** @brief The parameters for an LE connection have been updated.
     *
     *  This callback notifies the application that the connection
     *  parameters for an LE connection have been updated.
     *
     *  @param conn Connection object.
     *  @param interval Connection interval.
     *  @param latency Connection latency.
     *  @param timeout Connection supervision timeout.
     */
    void (*le_param_updated)(struct bt_conn *conn, uint16_t interval,
                 uint16_t latency, uint16_t timeout);
#if defined(CONFIG_BT_SMP)
    /** @brief Remote Identity Address has been resolved.
     *
     *  This callback notifies the application that a remote
     *  Identity Address has been resolved
     *
     *  @param conn Connection object.
     *  @param rpa Resolvable Private Address.
     *  @param identity Identity Address.
     */
    void (*identity_resolved)(struct bt_conn *conn,
                  const bt_addr_le_t *rpa,
                  const bt_addr_le_t *identity);
#endif /* CONFIG_BT_SMP */
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC)
    /** @brief The security level of a connection has changed.
     *
     *  This callback notifies the application that the security of a
     *  connection has changed.
     *
     *  The security level of the connection can either have been increased
     *  or remain unchanged. An increased security level means that the
     *  pairing procedure has been performed or the bond information from
     *  a previous connection has been applied. If the security level
     *  remains unchanged this means that the encryption key has been
     *  refreshed for the connection.
     *
     *  @param conn Connection object.
     *  @param level New security level of the connection.
     *  @param err Security error. Zero for success, non-zero otherwise.
     */
    void (*security_changed)(struct bt_conn *conn, bt_security_t level,
                 enum bt_security_err err);
#endif /* defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) */

#if defined(CONFIG_BT_REMOTE_INFO)
    /** @brief Remote information procedures has completed.
     *
     *  This callback notifies the application that the remote information
     *  has been retrieved from the remote peer.
     *
     *  @param conn Connection object.
     *  @param remote_info Connection information of remote device.
     */
    void (*remote_info_available)(struct bt_conn *conn,
                      struct bt_conn_remote_info *remote_info);
#endif /* defined(CONFIG_BT_REMOTE_INFO) */

#if defined(CONFIG_BT_USER_PHY_UPDATE)
    /** @brief The PHY of the connection has changed.
     *
     *  This callback notifies the application that the PHY of the
     *  connection has changed.
     *
     *  @param conn Connection object.
     *  @param info Connection LE PHY information.
     */
    void (*le_phy_updated)(struct bt_conn *conn,
                   struct bt_conn_le_phy_info *param);
#endif /* defined(CONFIG_BT_USER_PHY_UPDATE) */

#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
    /** @brief The data length parameters of the connection has changed.
     *
     *  This callback notifies the application that the maximum Link Layer
     *  payload length or transmission time has changed.
     *
     *  @param conn Connection object.
     *  @param info Connection data length information.
     */
    void (*le_data_len_updated)(struct bt_conn *conn,
                    struct bt_conn_le_data_len_info *info);
#endif /* defined(CONFIG_BT_USER_DATA_LEN_UPDATE) */

#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX)
    /** @brief Callback for IQ samples report collected when sampling
     *        CTE received by data channel PDU.
     *
     * @param conn      The connection object.
     * @param iq_report Report data for collected IQ samples.
     */
    void (*cte_report_cb)(struct bt_conn *conn,
                  const struct bt_df_conn_iq_samples_report *iq_report);
#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */

#if defined(CONFIG_BT_TRANSMIT_POWER_CONTROL)
    /** @brief LE Read Remote Transmit Power Level procedure has completed or LE
     *  Transmit Power Reporting event.
     *
     *  This callback notifies the application that either the remote transmit power level
     *  has been read from the peer or transmit power level has changed for the local or
     *  remote controller when transmit power reporting is enabled for the respective side
     *  using @ref bt_conn_le_set_tx_power_report_enable.
     *
     *  @param conn Connection object.
     *  @param report Transmit power report.
     */
    void (*tx_power_report)(struct bt_conn *conn,
                const struct bt_conn_le_tx_power_report *report);
#endif /* CONFIG_BT_TRANSMIT_POWER_CONTROL */

#if defined(CONFIG_BT_PATH_LOSS_MONITORING)
    /** @brief LE Path Loss Threshold event.
     *
     *  This callback notifies the application that there has been a path loss threshold
     *  crossing or reporting the initial path loss threshold zone after using
     *  @ref bt_conn_le_set_path_loss_mon_enable.
     *
     *  @param conn Connection object.
     *  @param report Path loss threshold report.
     */
    void (*path_loss_threshold_report)(struct bt_conn *conn,
                const struct bt_conn_le_path_loss_threshold_report *report);
#endif /* CONFIG_BT_PATH_LOSS_MONITORING */

#if defined(CONFIG_BT_SUBRATING)
    /** @brief LE Subrate Changed event.
     *
     *  This callback notifies the application that the subrating parameters
     *  of the connection may have changed.
     *  The connection subrating parameters will be unchanged
     *  if status is not BT_HCI_ERR_SUCCESS.
     *
     *  @param conn   Connection object.
     *  @param params New subrating parameters.
     */
    void (*subrate_changed)(struct bt_conn *conn,
                const struct bt_conn_le_subrate_changed *params);
#endif /* CONFIG_BT_SUBRATING */

#if defined(CONFIG_BT_CHANNEL_SOUNDING)
    /** @brief LE CS Read Remote Supported Capabilities Complete event.
     *
     *  This callback notifies the application that the remote channel
     *  sounding capabilities have been received from the peer.
     *
     *  @param conn Connection object.
     *  @param remote_cs_capabilities Remote Channel Sounding Capabilities.
     */
    void (*le_cs_remote_capabilities_available)(struct bt_conn *conn,
                            struct bt_conn_le_cs_capabilities *params);

    /** @brief LE CS Read Remote FAE Table Complete event.
     *
     *  This callback notifies the application that the remote mode-0
     *  FAE Table has been received from the peer.
     *
     *  @param conn Connection object.
     *  @param params FAE Table.
     */
    void (*le_cs_remote_fae_table_available)(struct bt_conn *conn,
                         struct bt_conn_le_cs_fae_table *params);

    /** @brief LE CS Config created.
     *
     *  This callback notifies the application that a Channel Sounding
     *  Configuration procedure has completed and a new CS config is created
     *
     *  @param conn Connection object.
     *  @param config CS configuration.
     */
    void (*le_cs_config_created)(struct bt_conn *conn, struct bt_conn_le_cs_config *config);

    /** @brief LE CS Config removed.
     *
     *  This callback notifies the application that a Channel Sounding
     *  Configuration procedure has completed and a CS config is removed
     *
     *  @param conn Connection object.
     *  @param config_id ID of the CS configuration that was removed.
     */
    void (*le_cs_config_removed)(struct bt_conn *conn, uint8_t config_id);

    /** @brief Subevent Results from a CS procedure are available.
     *
     * This callback notifies the user that CS subevent results are
     * available for the given connection object.
     *
     * @param conn Connection objects.
     * @param result Subevent results
     */
    void (*le_cs_subevent_data_available)(struct bt_conn *conn,
                          struct bt_conn_le_cs_subevent_result *result);

    /** @brief LE CS Security Enabled.
     *
     *  This callback notifies the application that a Channel Sounding
     *  Security Enable procedure has completed
     *
     *  @param conn Connection object.
     */
    void (*le_cs_security_enabled)(struct bt_conn *conn);

    /** @brief LE CS Procedure Enabled.
     *
     *  This callback notifies the application that a Channel Sounding
     *  Procedure Enable procedure has completed
     *
     *  @param conn Connection object.
     *  @param params CS Procedure Enable parameters
     */
    void (*le_cs_procedure_enabled)(
        struct bt_conn *conn, struct bt_conn_le_cs_procedure_enable_complete *params);

#endif

    /** @internal Internally used field for list handling */
    sys_snode_t _node;
};
復(fù)制代碼
2、這里我們還是來演示如何使用CONNECT和DISCONNECT這兩個回調(diào)來在BLE斷開和連接的時候處理相關(guān)任務(wù)。首先我們來寫一個結(jié)構(gòu)體ble_connection_callback ,這個結(jié)構(gòu)體里面包括兩個函數(shù)ble_on_connected和ble_on_connected

struct bt_conn_cb ble_connection_callback = {
    .connected    = ble_on_connected,
    .disconnected = ble_on_disconnected,
};
這里我們需要調(diào)用bt_conn_get_dst這個函數(shù)來獲取對端設(shè)備的MAC地址,并調(diào)用bt_addr_le_to_str將獲取到的地址轉(zhuǎn)成字符串用于打印。

復(fù)制代碼
/** @brief Get destination (peer) address of a connection.
 *
 *  @param conn Connection object.
 *
 *  @return Destination address.
 */
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn);

/** @brief Converts binary LE Bluetooth address to string.
 *
 *  @param addr Address of buffer containing binary LE Bluetooth address.
 *  @param str Address of user buffer with enough room to store
 *  formatted string containing binary LE address.
 *  @param len Length of data to be copied to user string buffer. Refer to
 *  BT_ADDR_LE_STR_LEN about recommended value.
 *
 *  @return Number of successfully formatted bytes from binary address.
 */
static inline int bt_addr_le_to_str(const bt_addr_le_t *addr, char *str,
                    size_t len)
復(fù)制代碼
在連接和斷開異常的時候我們還可以調(diào)用bt_hci_err_to_str來獲取相關(guān)的錯誤代碼。

復(fù)制代碼
/** Converts a HCI error to string.
 *
 * The error codes are described in the Bluetooth Core specification,
 * Vol 1, Part F, Section 2.
 *
 * The HCI documentation found in Vol 4, Part E,
 * describes when the different error codes are used.
 *
 * See also the defined BT_HCI_ERR_* macros.
 *
 * @return The string representation of the HCI error code.
 *         If @kconfig{CONFIG_BT_HCI_ERR_TO_STR} is not enabled,
 *         this just returns the empty string
 */
#if defined(CONFIG_BT_HCI_ERR_TO_STR)
const char *bt_hci_err_to_str(uint8_t hci_err);
#else
static inline const char *bt_hci_err_to_str(uint8_t hci_err)
復(fù)制代碼
最后調(diào)用dk_set_led_on和dk_set_led_off來點亮和熄滅nRF54L15 DK的LED1。

3、接下來我們來寫ble_on_connected和ble_on_connected這兩個回調(diào)函數(shù)

復(fù)制代碼
static void ble_on_connected(struct bt_conn *conn, uint8_t err)
{
    char addr[BT_ADDR_LE_STR_LEN];
  /*Print err code when the connection is error*/
    if (err) {
        LOG_ERR("Connection failed, err 0x%02x %s", err, bt_hci_err_to_str(err));
        return;
    }
  
  /*Print the MAC address of Central Deevice*/
    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    LOG_INF("Connected %s", addr);
  
  /*Count the coennection*/  
    current_conn = bt_conn_ref(conn);

    dk_set_led_on(DK_LED1)
);
}
復(fù)制代碼
復(fù)制代碼
static void ble_on_disconnected(struct bt_conn *conn, uint8_t reason)
{
    char addr[BT_ADDR_LE_STR_LEN];

    /*Get the MAC address of central and print it when the connection is disconnected*/
    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    LOG_INF("Disconnected: %s, reason 0x%02x %s", addr, reason, bt_hci_err_to_str(reason));
    
    /*Decrement a connection's reference count and off LED1 of DK*/
    if (current_conn) {
        bt_conn_unref(current_conn);
        current_conn = NULL;
        dk_set_led_off(DK_LED1);
    }
}
復(fù)制代碼
這里我們引入了一個全局變量current_conn用于計數(shù)已連接的設(shè)備數(shù)量,所以需要做一個聲明

static struct bt_conn *current_conn;
此外我們調(diào)用了LED1這個外設(shè)用來指示BLE的連接情況,所以我們還需要在main中初始化LED1這個外設(shè),這里我們需要加入一些頭文件并在prj.conf中使能相關(guān)的宏,最后在main中調(diào)用configure_gpio。

//Add LED and Button drivers Library file
#include <dk_buttons_and_leds.h>
# Enable DK LED and Buttons library
CONFIG_DK_LIBRARY=y
CONFIG_GPIO=y
復(fù)制代碼
static void configure_gpio(void)
{
    int err_code;

    err_code = dk_leds_init();
    if (err_code) {
        LOG_ERR("Cannot init LEDs (err: %d)", err_code);
    }
}
復(fù)制代碼
4、完成上述步驟之后,我們將ble_connection_callback這個函數(shù)注冊到bluetooth_init中

復(fù)制代碼
int bluetooth_init(struct bt_conn_cb *ble_cb)
{
    int err_code;
    
    LOG_INF("Initiallzing BLE");
    if (ble_cb == NULL)
    {
        return -NRFX_ERROR_NULL;
    }
    bt_conn_cb_register(ble_cb);

    err_code = bt_enable(bt_ready_callback);
    if(err_code)
    {
        LOG_ERR("BLE Enable returned %d",err_code);
        return err_code;
    }
    k_sem_take(&ble_init_ok, K_FOREVER);

    err_code = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), srd,
                  ARRAY_SIZE(srd));
    if (err_code) {
        LOG_ERR("Advertising failed to start (err_code %d)", err_code);
        return 0;
    }

    return err_code;
}
復(fù)制代碼
復(fù)制代碼
int main(void)
{
    int err_code;

    configure_gpio();

    err_code = bluetooth_init(&ble_connection_callback);
    if(err_code)
    {
        LOG_ERR("Bluetooth_init returnrd %d", err_code);
    }
    printf("Hello World! %s\n", CONFIG_BOARD_TARGET);
}
復(fù)制代碼
5、最后我們編譯下載工程到nRF54L15的DK中,使用nRF Connect APP連接可以看到連接和斷開之后分別打印了手機(jī)端的MAC地址


 同時DK的LED1在連接之后常亮


六、添加一個Service

1、從上面的圖片中可以看到,APP連接nRF54L15 DK之后只有一些通用的Service,這一步我們來演示如何添加一個自定義的Service,在nRF5 SDK中我們需要在service_init中初始化給相關(guān)的指針寫入值,但在NCS中我們可以直接調(diào)用BT_GATT_SERVICE_DEFINE來實現(xiàn)

復(fù)制代碼
/**
 *  @brief Statically define and register a service.
 *
 *  Helper macro to statically define and register a service.
 *
 *  @param _name Service name.
 */
#define BT_GATT_SERVICE_DEFINE(_name, ...)                \
    const struct bt_gatt_attr attr_##_name[] = { __VA_ARGS__ };    \
    const STRUCT_SECTION_ITERABLE(bt_gatt_service_static, _name) =    \
                    BT_GATT_SERVICE(attr_##_name)

#define _BT_GATT_ATTRS_ARRAY_DEFINE(n, _instances, _attrs_def)    \
    static struct bt_gatt_attr attrs_##n[] = _attrs_def(_instances[n])

#define _BT_GATT_SERVICE_ARRAY_ITEM(_n, _) BT_GATT_SERVICE(attrs_##_n)
復(fù)制代碼
2、這里我直接用了NUS服務(wù)的UUID,當(dāng)然你可以自己定義一個UUID看一下有什么效果

復(fù)制代碼
/** @brief UUID of the NUS Service. **/
#define BT_UUID_HOWD_VAL \
    BT_UUID_128_ENCODE(0x6e400001, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e)
#define BT_UUID_HOWD_SERVICE   BT_UUID_DECLARE_128(BT_UUID_HOWD_VAL)

#define SERVICE_NAME nus_svc
BT_GATT_SERVICE_DEFINE(SERVICE_NAME, 
                       BT_GATT_PRIMARY_SERVICE(BT_UUID_HOWD_SERVICE)
                       );
復(fù)制代碼
3、最后編譯一下工程下載的nRF54L15DK,并使用nRF Connect APP連接中可以看到多了一個NUS服務(wù)。


七、添加特征值并定義其屬性

1、完成上述步驟之后,BLE多了一個Service,但是這個Service中沒有Characteristic(特征值),這里我們還可以在BT_GATT_SERVICE_DEFINE中調(diào)用BT_GATT_CHARACTERISTIC,將特征值放在Service下面,這個結(jié)構(gòu)就像APP上Service下面有特征值那樣,非常直觀。這里我們?nèi)タ匆幌翨T_GATT_CHARACTERISTIC,會發(fā)現(xiàn)它一共有6個入?yún)?/div>

1)_uuid是特征值的UUID

2)_props是特征值的屬性,比如:read,write,Notify,indicate等

3)_perm是屬性的訪問權(quán)限,比如:普通、加密、配對等

4)_read、_write是分別是讀取和寫入的回調(diào)函數(shù)

5)_user_data是留給用戶自定義的一些數(shù)據(jù),一般不用

復(fù)制代碼
/**
 *  @brief Characteristic and Value Declaration Macro.
 *
 *  Helper macro to declare a characteristic attribute along with its
 *  attribute value.
 *
 *  @param _uuid Characteristic attribute uuid.
 *  @param _props Characteristic attribute properties,
 *                a bitmap of ``BT_GATT_CHRC_*`` macros.
 *  @param _perm Characteristic Attribute access permissions,
 *               a bitmap of @ref bt_gatt_perm values.
 *  @param _read Characteristic Attribute read callback
 *               (@ref bt_gatt_attr_read_func_t).
 *  @param _write Characteristic Attribute write callback
 *                (@ref bt_gatt_attr_write_func_t).
 *  @param _user_data Characteristic Attribute user data.
 */
#define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _user_data) \
    BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ,                 \
              bt_gatt_attr_read_chrc, NULL,                         \
              ((struct bt_gatt_chrc[]) {                            \
                BT_GATT_CHRC_INIT(_uuid, 0U, _props),           \
                           })),                         \
    BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data)
復(fù)制代碼
2、這里因為我們這個特征值的屬性是Read,所以我們需要在寫一個回調(diào),在里面將Button的鍵值通過GATT接口發(fā)送出去,這需要用到一個庫函數(shù)bt_gatt_attr_read,這個函數(shù)的聲明在gatt.h中,可以看到這個函數(shù)的作用是將本地數(shù)據(jù)通過Read屬性上傳

復(fù)制代碼
/** @brief Generic Read Attribute value helper.
 *
 *  Read attribute value from local database storing the result into buffer.
 *
 *  @param conn Connection object.
 *  @param attr Attribute to read.
 *  @param buf Buffer to store the value.
 *  @param buf_len Buffer length.
 *  @param offset Start offset.
 *  @param value Attribute value.
 *  @param value_len Length of the attribute value.
 *
 *  @return number of bytes read in case of success or negative values in
 *          case of error.
 */
ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
              void *buf, uint16_t buf_len, uint16_t offset,
              const void *value, uint16_t value_len);
復(fù)制代碼
3、我們來嘗試加一個Read屬性的特征值,讀取按鍵DK上按下的Button的鍵值,從BT_GATT_CHARACTERISTIC的注釋中可以看到,要使用這個宏需要寫一個回調(diào)函數(shù),首先來聲明以下這個回調(diào)函數(shù),并將bt_gatt_attr_read的入?yún)⒆鳛榛卣{(diào)函數(shù)的入?yún)?/div>

ssize_t read_button_characteristic_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                                   void *buf, uint16_t len, uint16_t offset);
這個函數(shù)的實際功能是在Read這個特征值的時候,將鍵值上傳給GATT,所以我們來定義一個全局變量來獲取button的鍵值

static uint8_t button_value = 0;
然后將button_value通過bt_gatt_attr_read上傳給GATT

復(fù)制代碼
ssize_t read_button_characteristic_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
        void *buf, uint16_t len, uint16_t offset)
{
    return bt_gatt_attr_read(conn, attr, buf, len, offset, &button_value, sizeof(button_value));

}
復(fù)制代碼
4、將這個回調(diào)放入BT_GATT_CHARACTERISTIC中,并寫入這個Read特征值的屬性等相關(guān)配置。

這里的特征值的UUID我用了NUS服務(wù)TX的特征值;

BT_GATT_CHRC_READ表示特征值的屬性是Read

BT_GATT_PERM_READ表示特征值的訪問權(quán)限為普通讀取

讀取的回調(diào)使用我們寫好的read_button_characteristic_cb這個函數(shù)

因為這個特征值只是Read屬性,所以不需要Write的回調(diào),也不需要用戶自定義數(shù)據(jù),所以最后兩個入?yún)⑻顚憺镹ULL

復(fù)制代碼
#define BT_UUID_NUS_TX_VAL \
    BT_UUID_128_ENCODE(0x6e400003, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e)
#define BT_UUID_NUS_TX        BT_UUID_DECLARE_128(BT_UUID_NUS_TX_VAL)

BT_GATT_SERVICE_DEFINE(SERVICE_NAME, 
BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE),
    BT_GATT_CHARACTERISTIC(BT_UUID_NUS_TX,
            BT_GATT_CHRC_READ,
            BT_GATT_PERM_READ,
            read_button_characteristic_cb, NULL, NULL),
);
復(fù)制代碼
GATT的屬性和權(quán)限除了上述兩個參數(shù)之外,還有其他類型,感興趣的讀者具體可以去gatt.h中查看相關(guān)注釋說明。

5、至此我們已經(jīng)完成了BLE數(shù)據(jù)上傳部分的代碼編寫,接下來我們來讀取Button的鍵值,并將這個值給到全局變量button_value即可。這里我們寫一個函數(shù)來傳遞button的鍵值

void set_button_value(uint8_t btn_value)
{
  button_value = btn_value;
}
我們還需要通過GPIO外設(shè)來讀取鍵值,和點亮LED一樣,需要先將DK的Button初始化,這里要調(diào)用dk_buttons_init來完成,它是一個庫函數(shù),函數(shù)聲明在dk_buttons_and_leds.h中。

復(fù)制代碼
/** @brief Initialize the library to read the button state.
 *
 *  @param  button_handler Callback handler for button state changes.
 *
 *  @retval 0           If the operation was successful.
 *                      Otherwise, a (negative) error code is returned.
 */
int dk_buttons_init(button_handler_t button_handler);

/**
 * @typedef button_handler_t
 * @brief Callback that is executed when a button state change is detected.
 *
 * @param button_state Bitmask of button states.
 * @param has_changed Bitmask that shows which buttons have changed.
 */
typedef void (*button_handler_t)(uint32_t button_state, uint32_t has_changed);
復(fù)制代碼
從注釋可以看到這個函數(shù)需要一個button的回調(diào)函數(shù),用于處理button時候的中斷。我們來寫這個回調(diào),在里面處理不同按鍵按下之后,定義不同的鍵值,并通過LOG打印鍵值,最后調(diào)用set_button_value將鍵值傳遞給全局變量button_value。

復(fù)制代碼
void button_handler(uint32_t button_state, uint32_t has_changed)
{
    int button_pressed = 0;

    if (has_changed & button_state) {
        if (DK_BTN1_MSK & has_changed) {
            button_pressed = 1;
        }

        if (DK_BTN2_MSK & has_changed) {
            button_pressed = 2;
        }

        if (DK_BTN3_MSK & has_changed) {
            button_pressed = 3;
        }

        if (DK_BTN4_MSK & has_changed) {
            button_pressed = 4;
        }
        LOG_INF("Button %d pressed", button_pressed);
        set_button_value(button_pressed);
    }
}
復(fù)制代碼
最后在GPIO初始化里面調(diào)用dk_buttons_init并寫入回調(diào)函數(shù)button_handler即可。

復(fù)制代碼
static void configure_gpio(void)
{
    int err_code;

    err_code = dk_buttons_init(button_handler);
    if (err_code) {
        LOG_ERR("Cannot init buttons (err: %d)", err_code);
    }

    err_code = dk_leds_init();
    if (err_code) {
        LOG_ERR("Cannot init LEDs (err: %d)", err_code);
    }
}
復(fù)制代碼
6、最后編譯工程,并使用nRF Connect APP連接nRF54L15DK的廣播,可以看到NUS Service下多了一個Read屬性,當(dāng)我們按下DK上的Button的時候,可以讀取到對應(yīng)的鍵值。




 持續(xù)未完........