BLE-NRF51822教程2-工程初始化流程

代碼講解基於資料包裏配套的 sdk5.1版本中的串口demo。高版本的sdk基本都是差不多的。

代碼在路徑xxxxx\keil\ARM\Device\Nordic\nrf51822\Board\pca10001\s110\experimental

一:main函數整體註釋:

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的協議棧實現是基於異步事件驅動的。


3、buttons_init

static void buttons_init(void)

{

  nrf_gpio_cfg_sense_input(WAKEUP_BUTTON_PIN, BUTTON_PULL, NRF_GPIO_PIN_SENSE_LOW);   

}

這裏的按鍵設置比較簡單,主要通過PIN_CNF寄存器來設置一個IO口來作爲來作爲sensing mechanism機制的引腳。這裏是設置了WAKEUP_BUTTON_PIN這個引腳來作爲這個功能,設置成低電平時觸發這個機制。而這個機制類似一個wakeup機制,當其被觸發時會產生一個DETECT signal而這個信號會將cpusystem 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);

}

初始化uart設置輸入輸出引腳,是否關閉流控。一般使用官方例子的時候都要先將流控關掉,HWFCFalse。然後打開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_t gap_conn_params;

    ble_gap_conn_sec_mode_t sec_mode;

//設置設備名的寫權限爲普通模式,則手機掃描到設備連接上後可以在第一個服務Geneic Access Service(有的只顯示UUID1800)中改寫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一樣,手機連上某個藍牙設備後可以從Generic Access 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_t  nus_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);

}


(1)ble_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_t  ble_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)

{

return err_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)

{

return err_code;

}

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

//這裏在上面註冊的服務中添加了兩個特徵值。

    err_code = rx_char_add(p_nus, p_nus_init);

    if (err_code != NRF_SUCCESS)

    {

        return err_code;

    }

 

    // Add TX Characteristic.

    err_code = tx_char_add(p_nus, p_nus_init);

    if (err_code != NRF_SUCCESS)

    {

        return err_code;

    }

return NRF_SUCCESS;

}

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

 

8、advertising_init

廣播參數的初始化

static void advertising_init(void)

{

uint32_t      err_code;

    ble_advdata_t  advdata;

ble_advdata_t  scanrsp;

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

//相比於一般可發現模式的廣播,有限可發現模式的廣播平率更快,但是隻能最多維持 //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_t cp_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);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章