日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一起学nRF51xx 21 -  蓝牙项目工程的初始化流程解读

發布時間:2025/4/5 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一起学nRF51xx 21 -  蓝牙项目工程的初始化流程解读 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

本節主要工作是對《一起學nRF51xx 20 -? 移植SDK藍牙例程》章節程序進行解讀。

示例詳解

基于硬件平臺:nrf51822ek_tm開發板。

本示例所用的最小系統板原理圖:

?

  • 程序啟動分析:
  • ?

    注,以下相關內容來自nrf51822ek_tm開發板的官方解析,個人覺得很不錯,故在此引用,官方作者:不離不棄qq574912883

    /**********************************************************************************************/

    int main(void)

    {

    //初始化 LED 指示燈,用來指示廣播和連接狀態

    leds_init();

    //初始化軟件定時器模塊

    timers_init();

    //設置按鍵作為 DETECT signal 用來喚醒 system off 模式,具體參看數據手冊 power 章節

    buttons_init();

    //主要設置 uart 的引腳,波特率。接收,發送中斷等。并開啟 uart 模塊中斷

    uart_init();

    //協議棧初試化, 設置時鐘, demo 里面設置為外部時鐘。并且注冊事件派發函數

    ble_stack_init();

    //GAP 一些參數的設置,設置設備名,設置 PPCP(外圍設備首選鏈接參數)(手機連上某個藍

    牙設備后可以從 Generic Access Service 中看到設置的這些參數)

    gap_params_init();

    //服務初始化。添加 uart 的串口服務。主要提供兩個特征值來供手機和板子以及電腦的通信

    services_init();

    //設置廣播數據以及掃描響應數據

    advertising_init();

    //鏈接參數設置。主要設置什么時候發起更新鏈接參數請求以及間隔和最大嘗試次數。

    conn_params_init();

    //安全參數初始化。

    sec_params_init();

    simple_uart_putstring(START_STRING);

    //設置廣播類型,白名單,間隔,超時等特性。并開始廣播。

    advertising_start();

    for (;;)

    {

    //電源管理,調用 arm0 的指令__WFE();進入睡眠

    power_manage();

    }

    }

    二:函數單獨解析:

    1 leds_init

    static void leds_init(void)

    {

    nrf_gpio_cfg_output(ADVERTISING_LED_PIN_NO);

    nrf_gpio_cfg_output(CONNECTED_LED_PIN_NO);

    }

    設置的 PIN_CONFIG 寄存器使能兩個引腳的作為輸出功能。用來當做指示燈指示廣播和鏈接的狀態。

    2 timers_init

    static void timers_init(void)

    {

    // Initialize timer module

    APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS,

    APP_TIMER_OP_QUEUE_SIZE, false);

    }

    初始化軟件定時器模塊,該定時器模塊并不是使用 timer0-2來實現定時功能。而是使用 51822中的 RTC1 來軟件模擬出定時器模塊。 RTC1 使用 32.768K 時鐘經過分頻后是時鐘來作為時鐘源。 所以該函數內部實現就是設置 RTC1 相關的寄存器和做一些初始化。 其原理和 timer 定時/計數器模塊類似。具體細節參考芯片數據手冊。

    ?

    APP_TIMER_PRESCALER:設置分頻系數。 (32.768K 來分頻)

    APP_TIMER_MAX_TIMERS:設置可以創建的最大定時器個數

    APP_TIMER_OP_QUEUE_SIZE:定時器操作隊列,因為是用 RTC 模擬的軟件定時器,因此內部是維護了一個軟件定時器的操作隊列

    False:不使用調度,調度模塊沒有細看。貌似 51822 關于調度的都是傳 False 不使用調度。51822 的協議棧實現是基于異步事件驅動的。

    3buttons_init

    static void buttons_init(void)

    {

    nrf_gpio_cfg_sense_input(WAKEUP_BUTTON_PIN,

    BUTTON_PULL,

    NRF_GPIO_PIN_SENSE_LOW);

    }

    這里的按鍵設置比較簡單,主要通過 PIN_CNF 寄存器來設置一個 IO 口來作為來作為 sensingmechanism 機制的引腳。這里是設置了 WAKEUP_BUTTON_PIN 這個引腳來作為這個功能,設置成低電平時觸發這個機制。而這個機制類似一個 wakeup 機制,當其被觸發時會產生一個DETECT signal 而這個信號會將 cpu system off 模式中喚醒。

    ?

    4 uart_init

    static void uart_init(void)

    {

    simple_uart_config(RTS_PIN_NUMBER, TX_PIN_NUMBER, CTS_PIN_NUMBER, RX_PIN_NUMBER,

    HWFC);

    NRF_UART0->INTENSET

    UART_INTENSET_RXDRDY_Enabled<<UART_INTENSET_RXDRDY_Pos;

    NVIC_SetPriority(UART0_IRQn, APP_IRQ_PRIORITY_LOW);

    NVIC_EnableIRQ(UART0_IRQn);

    /**@snippet [UART Initialization] */

    }

    初始化 uart 設置輸入輸出引腳,是否關閉流控。一般使用官方例子的時候都要先將流控關掉, HWFC 為 False。然后打開 uart 的接收中斷,打開 uart 模塊的中斷功能,以及設置優先級。 波特率在 simple_uart_config 中設置,該函數設置完引腳后使能 uart 開啟 uart 的接收和發送功能。

    ?

    5 ble_stack_init

    static void ble_stack_init(void)

    {

    // Initialize SoftDevice.

    SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, false);

    // Subscribe for BLE events.

    uint32_t err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);

    APP_ERROR_CHECK(err_code);

    }

    //設置 LFCLK(32.768K)的時鐘源(協議棧需要使用),這里設置為外部晶振。 False 為不使用調度。 softdevice_ble_evt_handler_set(ble_evt_dispatch);注冊事件派發程序,基礎 1-協議棧概述說明過,當 BLE 收到廣播,鏈接請求,對端設備數據等后底層處理完會上拋給上冊 app 一個事件,這個事件的上拋過程是協議棧觸發 SWI 中斷,在中斷內部將事件放入隊列,然后調用 app 中的 SWI 中斷。 App 中的 SWI 中斷會 get 隊列中的事件,并最終會調用注冊的ble_evt_dispatch 函數,這個函數再將事件發給各個服務以及模塊的事件處理函數來處理各個服務及模塊自己感興趣的事件。相關原理基礎 1-協議棧概述視頻教程中有說明。

    ?

    ?

    6gap_params_init

    設置必要的設備的 GAP 參數。

    static void gap_params_init(void)

    {

    uint32_t err_code;

    ble_gap_conn_params_tgap_conn_params;

    ble_gap_conn_sec_mode_tsec_mode;

    //設置設備名的寫權限為普通模式,則手機掃描到設備連接上后可以在第一個服務 GeneicAccess Service(有的只顯示 UUID 1800)中改寫 Device name.(有的 app 可能本身未實現改寫功能)

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);

    //設置設備名,該設備名就是在手機 app 掃描藍牙設備時顯示的名字。

    err_code = sd_ble_gap_device_name_set(&sec_mode,

    (const uint8_t *) DEVICE_NAME,

    strlen(DEVICE_NAME));

    APP_ERROR_CHECK(err_code);

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

    //設置外圍設備連接首選參數。同 device name一樣,手機連上某個藍牙設備后可以從 GenericAccess Service 中看到設置的這些參數。這個參數主要是讓中央設備在首次連接外設時可以讀取他們以及時調整連接參數。或者當中央設備以后重連該外設,并且之前保留了這些參數那么就免去了連接后可能需要的修改連接參數的麻煩。

    //當然,外圍設備也可以之后通過 sd_ble_gap_ppcp_get 來獲取之前設置的參數然后通過

    連接參數跟新請求函數向中央設備請求更改連接參數。

    gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;

    gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;

    gap_conn_params.slave_latency = SLAVE_LATENCY;

    gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;

    err_code = sd_ble_gap_ppcp_set(&gap_conn_params);

    APP_ERROR_CHECK(err_code);

    }

    7 services_init

    static void services_init(void)

    {

    uint32_t err_code;

    ble_nus_init_tnus_init;

    memset(&nus_init, 0, sizeof(nus_init));

    //注冊數據處理函數,這里處理的數據是收到手機發來的數據

    // nus_data_handler 就是將板子收到的數據通過串口打印到電腦上

    //實現了手機->開發板->電腦方向的數據流傳輸。

    nus_init.data_handler = nus_data_handler;

    err_code =ble_nus_init(&m_nus, &nus_init);

    APP_ERROR_CHECK(err_code);

    }

    1ble_nus_init 該函數中實現添加服務以及添加特征值

    uint32_t ble_nus_init(ble_nus_t * p_nus, constble_nus_init_t * p_nus_init)

    {

    uint32_t err_code;

    ble_uuid_tble_uuid;

    //設置基準 uuid

    ble_uuid128_t nus_base_uuid = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9,

    0xE0,0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E};

    if ((p_nus == NULL) || (p_nus_init == NULL))

    {

    return NRF_ERROR_NULL;

    }

    // 初始化連接句柄,因為現在并未與手機連接所以先賦值無效。

    //賦值數據處理函數,就是上面剛提到的打印收到的手機數據

    //設置 notify 是否使能的標志量,該標志量在手機連上板子并且使能了具

    //notfify 的特征值時(這里是 rx 特征值后面會講到),該標志會被設

    // 置。這個標志量僅僅只是一個類似 flag 的作用,甚至可能并未被

    // 用到。

    p_nus->conn_handle = BLE_CONN_HANDLE_INVALID;

    p_nus->data_handler = p_nus_init->data_handler;

    p_nus->is_notification_enabled = false;

    // 因為是自己定義的 uuid,所以需要調用該函數來賦值 p_nus->uuid_type

    //該函數會將這個 nus_base_uuid 放到協議棧內部的表中

    err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);

    if (err_code != NRF_SUCCESS)

    {

    returnerr_code;

    }

    //設置服務 uuid 以及 uuid_type(就是上面調用的函數或得的)

    ble_uuid.type = p_nus->uuid_type;

    ble_uuid.uuid = BLE_UUID_NUS_SERVICE;

    // 到這里就添加服務到協議棧內部表中了

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,

    &ble_uuid,

    &p_nus->service_handle);

    if (err_code != NRF_SUCCESS)

    {

    returnerr_code;

    }

    // 一個服務通常有幾個特征值

    //這里在上面注冊的服務中添加了兩個特征值。

    err_code = rx_char_add(p_nus, p_nus_init);

    if (err_code != NRF_SUCCESS)

    {

    returnerr_code;

    }

    // Add TX Characteristic.

    err_code = tx_char_add(p_nus, p_nus_init);

    if (err_code != NRF_SUCCESS)

    {

    returnerr_code;

    }

    return NRF_SUCCESS;

    }

    本篇教程主要說明協議棧的整體框架和。關于服務的創建和特征值的添加。這里不做細將,主要因為特征值的添加涉及到很多的概念和參數設置。后面分單獨發一篇教程針對如何創建自己的服務并添加特征值。

    ?

    8advertising_init

    廣播參數的初始化

    static void advertising_init(void)

    {

    uint32_t err_code;

    ble_advdata_tadvdata;

    ble_advdata_tscanrsp;

    //該標志主要設置廣播類型為有限可發現模式,并且設置不支持經典藍牙

    //相比于一般可發現模式的廣播,有限可發現模式的廣播平率更快,但是只能最多維持

    //30s

    uint8_t flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;

    //設置需要廣播的 uuid, 就是上面主測的服務 uuid

    ble_uuid_tadv_uuids[] = {{BLE_UUID_NUS_SERVICE, m_nus.uuid_type}};

    //這里設置廣播的名字為全名,設置標志,就是上面提到的。

    //appearance 外觀,他就是一個整形值,代表設備是一個手環,手機什么的。

    memset(&advdata, 0, sizeof(advdata));

    advdata.name_type = BLE_ADVDATA_FULL_NAME;

    advdata.include_appearance = false;

    advdata.flags.size = sizeof(flags);

    advdata.flags.p_data = &flags;

    //這里設置的是掃描響應數據。該數據在設備收到掃描請求的時候才會發出去。

    //有時候需要廣播的數據可能太多,廣播包中放不下,那么就可以放在掃描響應

    //數據中,這樣對端設備便可以通過掃描請求來或得剩下的數據。

    memset(&scanrsp, 0, sizeof(scanrsp));

    scanrsp.uuids_complete.uuid_cnt = sizeof(adv_uuids) / sizeof(adv_uuids[0]);

    scanrsp.uuids_complete.p_uuids =adv_uuids;

    err_code = ble_advdata_set(&advdata, &scanrsp);

    APP_ERROR_CHECK(err_code);

    }

    ?

    ?

    9 conn_params_init

    設置連接參數

    static void conn_params_init(void)

    {

    uint32_t err_code;

    ble_conn_params_init_tcp_init;

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

    //這里連接參數設置為 NULL 的原因是前面的 gap_params_init 函數中已經設置了連接

    //參數并調用了 sd_ble_gap_ppcp_set 將參數設置到了協議棧中。所以這里既是不設置,

    //下面的 ble_conn_params_init 會自動判斷是否為空,為空就調用提取函數,從協議棧

    //中提取之前注冊的參數。

    cp_init.p_conn_params = NULL;

    //下面主要是設置一些連接參數更新的事件,以及更新周期和最大最大嘗試更新次數。

    //部分參數不好描述,視頻中會說明。

    cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;

    cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;

    cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;

    cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;

    cp_init.disconnect_on_fail = false;

    cp_init.evt_handler = on_conn_params_evt;

    cp_init.error_handler = conn_params_error_handler;

    err_code =ble_conn_params_init(&cp_init);

    APP_ERROR_CHECK(err_code);

    }

    10 sec_params_init

    安全參數的初始化。 主要設置超時時間:比如配對過程中某一步的確認超過這個時間還未收到那么便是超時。 APP 會收到SD 上拋的狀態事件,狀態為超時

    Bond: 是否綁定。如果需要綁定,配對過程會有第三步的秘鑰分發,然后 app 將秘鑰存儲在falsh 這樣下次就可以避免了下次重復配對的過程。

    MITM: 是否需要中間人保護。

    Io_caps:本設備的 I/O 能力。比如有顯示屏,有鍵盤。

    :當使能了 MITM 并且兩端設備一個有鍵盤,一個有顯示屏時,配對過程中就會顯示一個配對碼,對端設備通過鍵盤再輸入。

    如果沒有 MITM 保護配對過程中的信息是很容易被監聽到的。但是如果有了 MITM 因為這個配對碼信息是一端顯示一端輸入,并不會通過鏈路傳輸。因為除了兩端設備不會有第三個設備知道。因此后續的鏈路加密就很難被破解。

    OOB:與 MITM 類似,只是配對碼不是通過鍵盤輸入而是通過兩端設備別的通信通道傳輸,比如 NFC,當然前提是該通信鏈路是安全的。不如也沒必要繞個彎而不直接用 BLE來傳輸了。后面就是設置加密秘鑰的最大和最小值。 加密秘鑰的大小在 7-16 字節之間配對的過程相對比較復雜,這里不做理論解釋。后期需要的話會單獨做一片配對的詳細教程,群文件中有我上傳了一個作為從機的配對歷程也是基于 uart,當主機在使能有第一個特征值的 notify 時便會觸發配對,配對碼是通過串口打印的。使用的隨機產生的。當然也可以設置為靜態的。

    void sec_params_init(void)

    {

    m_sec_params.timeout = SEC_PARAM_TIMEOUT;

    m_sec_params.bond = SEC_PARAM_BOND;

    m_sec_params.mitm = SEC_PARAM_MITM;

    m_sec_params.io_caps = SEC_PARAM_IO_CAPABILITIES;

    m_sec_params.oob = SEC_PARAM_OOB;

    m_sec_params.min_key_size = SEC_PARAM_MIN_KEY_SIZE;

    m_sec_params.max_key_size = SEC_PARAM_MAX_KEY_SIZE;

    }

    11 advertising_start

    static void advertising_start(void)

    {

    uint32_t err_code;

    ble_gap_adv_params_t adv_params;

    memset(&adv_params, 0, sizeof(adv_params));

    //設置廣播類型為通用廣播.

    廣播類型有四種:

    通用廣播:用途最廣的廣播方式。可以被掃描到,以及可以被連接

    定向廣播:用來快速建立和目標設備建立連接。報文中包含自己以及目標地址。不可連接廣播:只廣播數據,不可以被掃描以及連接。可發現廣播;可以被掃描(回復掃描響應數據),不可以被連接。

    adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;

    //如果廣播方式為定向廣播,這里添目標設備的地址

    adv_params.p_peer_addr = NULL;

    //設置過濾規則。

    //可設置為是否過濾掉非白名單中的掃描請以及非白名單中的連接請求或者兩者都過濾。

    adv_params.fp = BLE_GAP_ADV_FP_ANY;

    //設置廣播間隔和廣播超時,超時時間到期如果設備還未連接那么 app 會收到協議棧上

    //拋的廣播超時時間。 App 可以做自己想做的處理,比如讓設備進入睡眠。

    adv_params.interval = APP_ADV_INTERVAL;

    adv_params.timeout = APP_ADV_TIMEOUT_IN_SECONDS;

    //開啟廣播

    err_code = sd_ble_gap_adv_start(&adv_params);

    APP_ERROR_CHECK(err_code);

    nrf_gpio_pin_set(ADVERTISING_LED_PIN_NO);

    }

    /**********************************************************************************************/

    注,以上相關內容來自nrf51822ek_tm開發板的官方解析,個人覺得很不錯,故在此引用,官方作者:不離不棄qq574912883

    ?

  • 應用程序與協議之間的關系
  • Nordic提供的協藍牙協議棧形式是HEX文件,那個應用程序如何調用協議棧中的API接口函數呢,答案是應用程序通過SVC指令,解發藍牙協議棧中的SVC中斷,協議棧在SVC中斷中通過服務號(執行SVC指令中的帶入的一個數字)確定要執行的具體代碼/接口/函數,

    以ble_stack_init函數為例時行分析,在內部softdevice_enable函數,softdevice_enable又調用了sd_ble_enable接口。

    當對sd_ble_enable按快捷鍵F12時,發現MDK提示找不到相關原型說明通知,

    在工程中進行sd_ble_enable全局查找發現在ble.h文件中有相關定義:

    SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(ble_enable_params_t * p_ble_enable_params, uint32_t * p_app_ram_base));

    同時,在nrf_svc.h中可以看到關到SVCALL宏的定義為:

    #define SVCALL(number, return_type, signature) return_type __svc(number) signature

    SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(ble_enable_params_t * p_ble_enable_params, uint32_t * p_app_ram_base));

    可以寫成:

    uint32_t __svc(SD_BLE_ENABLE) sd_ble_enable(ble_enable_params_t * p_ble_enable_params, uint32_t * p_app_ram_base)

    這句話的作用讓編譯器在程序中看到sd_ble_enable接口時,產生一個SVC調用,SVC服務號為SD_BLE_ENABLE,并按uint32_t? sd_ble_enable(ble_enable_params_t * p_ble_enable_params, uint32_t * p_app_ram_base)形式把相關的參數存放地址放入到堆棧中,實現參數傳遞功能。這也是SVC調用的基本原理。

    ?

    ?

    應用程序與協議棧之間的參數傳遞則是通過堆棧實現。協議棧與應用程序通信是通過SWI軟中斷來實現的。這里給大家介紹一個相關的博文地址:

    https://blog.csdn.net/wulazula/article/details/80847262

    ?

    文中源碼資料下載,在公眾號里給十三發消息:

    下載|一起學nRF51xx 21

    ?

    關注十三公眾號

    ?

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的一起学nRF51xx 21 -  蓝牙项目工程的初始化流程解读的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。