BLE-NRF51822教程4-串口BLE解析
本講逐行代碼解析官方串口BLE例程demo
PS:?基于SDK5.1
?
主要分一下幾個(gè)部分:
1?:Main函數(shù)的整體注釋
2?:函數(shù)單獨(dú)解析。
3?:接收串口數(shù)據(jù)并發(fā)送給對(duì)端設(shè)備
4?:接收手機(jī)數(shù)據(jù)并通過(guò)串口打印
?
Ps?:第一和第二部分我在教程工程初始化流程中已經(jīng)詳細(xì)說(shuō)明這里直接復(fù)制過(guò)來(lái),做了一些修改以及添加了關(guān)于添加服務(wù)和添加特征值的講解,如果之前看過(guò)可以直接看下?2函數(shù)單獨(dú)解析中的 服務(wù)初始化后面添加的內(nèi)容即可
一:main函數(shù)整體注釋:
int main(void)
{
?//初始化LED指示燈,用來(lái)指示廣播和連接狀態(tài)
leds_init();
//初始化軟件定時(shí)器模塊
timers_init();
//設(shè)置按鍵作為?DETECT signal 用來(lái)喚醒system off模式,具體參看數(shù)據(jù)手冊(cè)power?章節(jié)
buttons_init();
//主要設(shè)置uart的引腳,波特率。接收,發(fā)送中斷等。并開(kāi)啟uart模塊中斷
uart_init();
//協(xié)議棧初試化,設(shè)置時(shí)鐘,demo里面設(shè)置為外部時(shí)鐘。并且注冊(cè)事件派發(fā)函數(shù)
ble_stack_init();
//GAP一些參數(shù)的設(shè)置,設(shè)置設(shè)備名,設(shè)置PPCP(外圍設(shè)備首選鏈接參數(shù))。(手機(jī)連上某個(gè)藍(lán)牙設(shè)備后可以從Generic Access Service中看到設(shè)置的這些參數(shù))
gap_params_init();
//服務(wù)初始化。添加uart的串口服務(wù)。主要提供兩個(gè)特征值來(lái)供手機(jī)和板子以及電腦的通信
services_init();
//設(shè)置廣播數(shù)據(jù)以及掃描響應(yīng)數(shù)據(jù)
advertising_init();
//鏈接參數(shù)設(shè)置。主要設(shè)置什么時(shí)候發(fā)起更新鏈接參數(shù)請(qǐng)求以及間隔和最大嘗試次數(shù)。
conn_params_init();
//安全參數(shù)初始化。
sec_params_init();
? simple_uart_putstring(START_STRING);
//設(shè)置廣播類(lèi)型,白名單,間隔,超時(shí)等特性。并開(kāi)始廣播。
advertising_start();
for (;;)
{
//電源管理,調(diào)用arm0的指令__WFE();進(jìn)入睡眠
power_manage();
}
}
二:函數(shù)單獨(dú)解析:
1 leds_init
static void leds_init(void)
{
nrf_gpio_cfg_output(ADVERTISING_LED_PIN_NO);
nrf_gpio_cfg_output(CONNECTED_LED_PIN_NO);
}
設(shè)置的PIN_CONFIG寄存器使能兩個(gè)引腳的作為輸出功能。用來(lái)當(dāng)做指示燈指示廣播和鏈接的狀態(tài)。
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);
}
初始化軟件定時(shí)器模塊,該定時(shí)器模塊并不是使用timer0-2來(lái)實(shí)現(xiàn)定時(shí)功能。而是使用51822中的RTC1?來(lái)軟件模擬出定時(shí)器模塊。RTC1使用32.768K時(shí)鐘經(jīng)過(guò)分頻后是時(shí)鐘來(lái)作為時(shí)鐘源。所以該函數(shù)內(nèi)部實(shí)現(xiàn)就是設(shè)置RTC1相關(guān)的寄存器和做一些初始化。其原理和timer?定時(shí)/計(jì)數(shù)器模塊類(lèi)似。具體細(xì)節(jié)參考芯片數(shù)據(jù)手冊(cè)。
?
APP_TIMER_PRESCALER:設(shè)置分頻系數(shù)。(以32.768K來(lái)分頻)
APP_TIMER_MAX_TIMERS:設(shè)置可以創(chuàng)建的最大定時(shí)器個(gè)數(shù)
APP_TIMER_OP_QUEUE_SIZE:定時(shí)器操作隊(duì)列,因?yàn)槭怯肦TC模擬的軟件定時(shí)器,因此內(nèi)部?????????????????????????????????????????????????????? ?????????是維護(hù)了一個(gè)軟件定時(shí)器的操作隊(duì)列
False:不使用調(diào)度,調(diào)度模塊沒(méi)有細(xì)看。51822關(guān)于調(diào)度的很多都是傳False不使用調(diào)?????????????????????度。??????????????????
3buttons_init
static void buttons_init(void)
{
nrf_gpio_cfg_sense_input(WAKEUP_BUTTON_PIN,
???????????????????????????? BUTTON_PULL,
???????????????????????????? NRF_GPIO_PIN_SENSE_LOW);???
}
這里的按鍵設(shè)置比較簡(jiǎn)單,主要通過(guò)PIN_CNF寄存器來(lái)設(shè)置一個(gè)IO口來(lái)作為來(lái)作為sensing mechanism機(jī)制的引腳。這里是設(shè)置了WAKEUP_BUTTON_PIN這個(gè)引腳來(lái)作為這個(gè)功能,設(shè)置成低電平時(shí)觸發(fā)這個(gè)機(jī)制。而這個(gè)機(jī)制類(lèi)似一個(gè)wakeup機(jī)制,當(dāng)其被觸發(fā)時(shí)會(huì)產(chǎn)生一個(gè)DETECT signal而這個(gè)信號(hào)會(huì)將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; </uart_intenset_rxdrdy_pos;<>
?
NVIC_SetPriority(UART0_IRQn, APP_IRQ_PRIORITY_LOW);
NVIC_EnableIRQ(UART0_IRQn);
??? /**@snippet [UART Initialization] */
}
初始化uart設(shè)置輸入輸出引腳,是否關(guān)閉流控。一般使用官方例子的時(shí)候都要先將流控關(guān)掉,HWFC為False。然后打開(kāi)uart的接收中斷,打開(kāi)uart模塊的中斷功能,以及設(shè)置優(yōu)先級(jí)。?????????波特率在simple_uart_config中設(shè)置,該函數(shù)設(shè)置完引腳后使能uart,開(kāi)啟uart的接收和發(fā)送功能。
?
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);
}
設(shè)置LFCLK(32.768K)的時(shí)鐘源(協(xié)議棧需要使用),這里設(shè)置為外部晶振。False為不使用調(diào)度。softdevice_ble_evt_handler_set(ble_evt_dispatch);注冊(cè)事件派發(fā)程序,基礎(chǔ)1-協(xié)議棧概述說(shuō)明過(guò),當(dāng)BLE收到廣播,鏈接請(qǐng)求,對(duì)端設(shè)備數(shù)據(jù)等后底層處理完會(huì)上拋給上冊(cè)app一個(gè)事件,這個(gè)事件的上拋過(guò)程是協(xié)議棧觸發(fā)SWI中斷,在中斷內(nèi)部將事件放入隊(duì)列,然后調(diào)用app中的SWI中斷。App中的SWI中斷會(huì)get隊(duì)列中的事件,并最終會(huì)調(diào)用注冊(cè)的ble_evt_dispatch函數(shù),這個(gè)函數(shù)再將事件發(fā)給各個(gè)服務(wù)以及模塊的事件處理函數(shù)來(lái)處理各個(gè)服務(wù)及模塊自己感興趣的事件。相關(guān)原理基礎(chǔ)1-協(xié)議棧概述視頻教程中有說(shuō)明。
?
6gap_params_init
設(shè)置必要的設(shè)備的GAP參數(shù)。
static void gap_params_init(void)
{
??? uint32_t??????????????? err_code;
????ble_gap_conn_params_tgap_conn_params;
????ble_gap_conn_sec_mode_tsec_mode;
?
//設(shè)置設(shè)備名的寫(xiě)權(quán)限為普通模式,則手機(jī)掃描到設(shè)備連接上后可以在第一個(gè)服務(wù)Geneic Access Service(有的只顯示UUID為1800)中改寫(xiě)Device name.(有的app可能本身未實(shí)現(xiàn)改寫(xiě)功能)
? ? ?BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
//設(shè)置設(shè)備名,該設(shè)備名就是在手機(jī)app掃描藍(lán)牙設(shè)備時(shí)顯示的名字。
????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));
//設(shè)置外圍設(shè)備連接首選參數(shù)。同device name一樣,手機(jī)連上某個(gè)藍(lán)牙設(shè)備后可以從Generic Access Service中看到設(shè)置的這些參數(shù)。這個(gè)參數(shù)主要是讓中央設(shè)備在首次連接外設(shè)時(shí)可以讀取他們以及時(shí)調(diào)整連接參數(shù)。或者當(dāng)中央設(shè)備以后重連該外設(shè),并且之前保留了這些參數(shù)那么就免去了連接后可能需要的修改連接參數(shù)的麻煩。
//當(dāng)然,外圍設(shè)備也可以之后通過(guò)sd_ble_gap_ppcp_get來(lái)獲取之前設(shè)置的參數(shù)然后通過(guò)連接參數(shù)跟新請(qǐng)求函數(shù)向中央設(shè)備請(qǐng)求更改連接參數(shù)。
????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));
????????
//注冊(cè)數(shù)據(jù)處理函數(shù),這里處理的數(shù)據(jù)是收到手機(jī)發(fā)來(lái)的數(shù)據(jù)
// nus_data_handler就是將板子收到的數(shù)據(jù)通過(guò)串口打印到電腦上
//實(shí)現(xiàn)了手機(jī)->開(kāi)發(fā)板->電腦方向的數(shù)據(jù)流傳輸。
nus_init.data_handler = nus_data_handler;
?
err_code =ble_nus_init(&m_nus, &nus_init);
??? APP_ERROR_CHECK(err_code);
}
7.1?ble_nus_init該函數(shù)中實(shí)現(xiàn)添加服務(wù)以及添加特征值
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;
//設(shè)置基準(zhǔn)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;
??? }
?
?//?初始化連接句柄,因?yàn)楝F(xiàn)在并未與手機(jī)連接所以先賦值無(wú)效。
?? //賦值數(shù)據(jù)處理函數(shù),就是上面剛提到的打印收到的手機(jī)數(shù)據(jù)
?? //設(shè)置notify是否使能的標(biāo)志量,該標(biāo)志量在手機(jī)連上板子并且使能了具?????????????????????????????? //有notfify的特征值時(shí)(這里是rx特征值后面會(huì)講到),該標(biāo)志會(huì)被設(shè)??????????????????????????????????? //????置。這個(gè)標(biāo)志量?jī)H僅只是一個(gè)類(lèi)似flag的作用,甚至可能并未被
??????????? //?用到。
p_nus->conn_handle????????????? = BLE_CONN_HANDLE_INVALID;
p_nus->data_handler???????????? = p_nus_init->data_handler;
p_nus->is_notification_enabled? ??? = false;
?
??? //?因?yàn)槭亲约憾x的uuid,所以需要調(diào)用該函數(shù)來(lái)賦值p_nus->uuid_type
??????????? //該函數(shù)會(huì)將這個(gè)nus_base_uuid放到協(xié)議棧內(nèi)部的表中
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
if (err_code != NRF_SUCCESS)
??? {
returnerr_code;
??? }
?
??????????? //設(shè)置服務(wù)uuid以及uuid_type(就是上面調(diào)用的函數(shù)或得的)
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
?
??? //?到這里就添加服務(wù)到協(xié)議棧內(nèi)部表中了
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;
?}
?
??? //?一個(gè)服務(wù)通常有幾個(gè)特征值
??????????? //這里在上面注冊(cè)的服務(wù)中添加了兩個(gè)特征值。
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;
}
7.1.1? rx_char_add
這個(gè)特征用來(lái)將板子從串口收到的數(shù)據(jù)通過(guò)該特征值使用notify方式發(fā)送給手機(jī)
代碼太長(zhǎng)截圖注釋:
PS:后面標(biāo)記寫(xiě)的有點(diǎn)問(wèn)題。是設(shè)置讀寫(xiě)不需要加密或MITM(其實(shí)就是設(shè)置安全模式和等級(jí))
7.1.2tx_char_add
這個(gè)添加的特征值用來(lái)接收手機(jī)發(fā)送給板子的數(shù)據(jù)。
和Rx?特征值的設(shè)置基本一致,只是將notify?功能的設(shè)置去掉了改成了設(shè)置成可寫(xiě)。其他的代碼基本是一樣的。這里就不貼代碼了。
?
8 advertising_init
廣播參數(shù)的初始化
static void advertising_init(void)
{
??? uint32_t????? err_code;
????ble_advdata_tadvdata;
????ble_advdata_tscanrsp;
//該標(biāo)志主要設(shè)置廣播類(lèi)型為有限可發(fā)現(xiàn)模式,并且設(shè)置不支持經(jīng)典藍(lán)牙
//相比于一般可發(fā)現(xiàn)模式的廣播,有限可發(fā)現(xiàn)模式的廣播平率更快,但是只能最多維持?//30s
????uint8_t?????? flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
?
???????? //設(shè)置需要廣播的uuid,就是上面主測(cè)的服務(wù)uuid
????ble_uuid_tadv_uuids[] = {{BLE_UUID_NUS_SERVICE, m_nus.uuid_type}};
?
?
???????? //這里設(shè)置廣播的名字為全名,設(shè)置標(biāo)志,就是上面提到的。
???????? //appearance為”外觀”,他就是一個(gè)整形值,代表設(shè)備是一個(gè)手環(huán),手機(jī)什么的。
????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;
?
???????? //這里設(shè)置的是掃描響應(yīng)數(shù)據(jù)。該數(shù)據(jù)在設(shè)備收到掃描請(qǐng)求的時(shí)候才會(huì)發(fā)出去。
???????? //有時(shí)候需要廣播的數(shù)據(jù)可能太多,廣播包中放不下,那么就可以放在掃描響應(yīng)
???????? //數(shù)據(jù)中,這樣對(duì)端設(shè)備便可以通過(guò)掃描請(qǐng)求來(lái)或得剩下的數(shù)據(jù)。
????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
設(shè)置連接參數(shù)
static void conn_params_init(void)
{
??? uint32_t?????????????? err_code;
????ble_conn_params_init_tcp_init;
?
????memset(&cp_init, 0, sizeof(cp_init));
?
???????? //這里連接參數(shù)設(shè)置為NULL的原因是前面的gap_params_init函數(shù)中已經(jīng)設(shè)置了連接?????? //參數(shù)并調(diào)用了sd_ble_gap_ppcp_set將參數(shù)設(shè)置到了協(xié)議棧中。所以這里既是不設(shè)置,
???????? //下面的ble_conn_params_init會(huì)自動(dòng)判斷是否為空,為空就調(diào)用提取函數(shù),從協(xié)議棧
???????? //中提取之前注冊(cè)的參數(shù)。
cp_init.p_conn_params????????????????? = NULL;
//下面主要是設(shè)置一些連接參數(shù)更新的事件,以及更新周期和最大最大嘗試更新次數(shù)。
//部分參數(shù)不好描述,視頻中會(huì)說(shuō)明。
????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
安全參數(shù)的初始化。主要設(shè)置
超時(shí)時(shí)間:比如配對(duì)過(guò)程中某一步的確認(rèn)超過(guò)這個(gè)時(shí)間還未收到那么便是超時(shí)。APP會(huì)收到SD上拋的狀態(tài)事件,狀態(tài)為超時(shí)
Bond:?是否綁定。如果需要綁定,配對(duì)過(guò)程會(huì)有第三步的秘鑰分發(fā),然后app將秘鑰存儲(chǔ)在falsh這樣下次就可以避免了下次重復(fù)配對(duì)的過(guò)程。
MITM:?是否需要中間人保護(hù)。
Io_caps:本設(shè)備的I/O能力。比如有顯示屏,有鍵盤(pán)。
?
:當(dāng)使能了MITM?并且兩端設(shè)備一個(gè)有鍵盤(pán),一個(gè)有顯示屏?xí)r,配對(duì)過(guò)程中就會(huì)顯示一個(gè)配對(duì)碼,對(duì)端設(shè)備通過(guò)鍵盤(pán)再輸入。
如果沒(méi)有MITM保護(hù)配對(duì)過(guò)程中的信息是很容易被監(jiān)聽(tīng)到的。但是如果有了MITM因?yàn)檫@個(gè)配對(duì)碼信息是一端顯示一端輸入,并不會(huì)通過(guò)鏈路傳輸。因?yàn)槌藘啥嗽O(shè)備不會(huì)有第三個(gè)設(shè)備知道。因此后續(xù)的鏈路加密就很難被破解。
OOB:與MITM類(lèi)似,只是配對(duì)碼不是通過(guò)鍵盤(pán)輸入而是通過(guò)兩端設(shè)備別的通信通道傳輸,比如NFC,當(dāng)然前提是該通信鏈路是安全的。不如也沒(méi)必要繞個(gè)彎而不直接用BLE來(lái)傳輸了。
后面就是設(shè)置加密秘鑰的最大和最小值。加密秘鑰的大小在7-16字節(jié)之間
?
配對(duì)的過(guò)程相對(duì)比較復(fù)雜,這里不做理論解釋。后期需要的話會(huì)單獨(dú)做一片配對(duì)的詳細(xì)教程,群文件中有我上傳了一個(gè)作為從機(jī)的配對(duì)歷程也是基于uart,當(dāng)主機(jī)在使能有第一個(gè)特征值的notify時(shí)便會(huì)觸發(fā)配對(duì),配對(duì)碼是通過(guò)串口打印的。使用的隨機(jī)產(chǎn)生的。當(dāng)然也可以設(shè)置為靜態(tài)的。
?
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));
?
???????? //設(shè)置廣播類(lèi)型為通用廣播.
????????廣播類(lèi)型有四種:
通用廣播:用途最廣的廣播方式。可以被掃描到,以及可以被連接
定向廣播:用來(lái)快速建立和目標(biāo)設(shè)備建立連接。報(bào)文中包含自己以及目標(biāo)地址。
不可連接廣播:只廣播數(shù)據(jù),不可以被掃描以及連接。
可發(fā)現(xiàn)廣播;可以被掃描(回復(fù)掃描響應(yīng)數(shù)據(jù)),不可以被連接。
?
adv_params.type??????? = BLE_GAP_ADV_TYPE_ADV_IND;
//如果廣播方式為定向廣播,這里添目標(biāo)設(shè)備的地址
adv_params.p_peer_addr = NULL;?????????
//設(shè)置過(guò)濾規(guī)則。
//可設(shè)置為是否過(guò)濾掉非白名單中的掃描請(qǐng)以及非白名單中的連接請(qǐng)求或者兩者都過(guò)濾。
??? adv_params.fp????????? = BLE_GAP_ADV_FP_ANY;
//設(shè)置廣播間隔和廣播超時(shí),超時(shí)時(shí)間到期如果設(shè)備還未連接那么app會(huì)收到協(xié)議棧上
//拋的廣播超時(shí)時(shí)間。App可以做自己想做的處理,比如讓設(shè)備進(jìn)入睡眠。
adv_params.interval??? = APP_ADV_INTERVAL;
??? adv_params.timeout???? = APP_ADV_TIMEOUT_IN_SECONDS;
//開(kāi)啟廣播
??? err_code = sd_ble_gap_adv_start(&adv_params);
??? APP_ERROR_CHECK(err_code);
?
??? nrf_gpio_pin_set(ADVERTISING_LED_PIN_NO);
}
?
三接收串口數(shù)據(jù)并發(fā)送給對(duì)端設(shè)備
上面介紹的整個(gè)初始化完成后,設(shè)備便進(jìn)入睡眠模式,每當(dāng)廣播間隔到期會(huì)發(fā)送一次廣播。直到有設(shè)備發(fā)來(lái)連接請(qǐng)求,當(dāng)設(shè)備連接上手機(jī)后邊繼續(xù)處于睡眠狀態(tài)等待”事件”的發(fā)生
?
先來(lái)分析電腦à開(kāi)發(fā)板à手機(jī)方向的數(shù)據(jù)流
在main?函數(shù)的串口初始化程序uart_init的最后打開(kāi)了串口的接收中斷。
那么這個(gè)方向的數(shù)據(jù)流的起點(diǎn)就是在串口中斷中收到電腦上發(fā)來(lái)的數(shù)據(jù)為起點(diǎn)
?
Uart中斷函數(shù)在main函數(shù)上方
void UART0_IRQHandler(void)
{
??? static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
??? static uint8_t index = 0;
??? uint32_t err_code;
???????? uint8_t temp;
????????
???????? //取得電腦串口發(fā)過(guò)來(lái)的數(shù)據(jù)
??? data_array[index] = simple_uart_get();
??? index++;
???????? //判斷串口發(fā)送給來(lái)的數(shù)據(jù)是否達(dá)到20的字節(jié),或者是不是發(fā)送了字母’q’。如果滿(mǎn)足
???????? //調(diào)用發(fā)送函數(shù)將收到的串口數(shù)據(jù)發(fā)送給手機(jī)。否則不發(fā)送等待知道滿(mǎn)足條件。
??? // (這里通常新手說(shuō)手機(jī)收不到數(shù)據(jù)的原因,因?yàn)闆](méi)輸入達(dá)到20個(gè)字節(jié))
??? if ((data_array[index - 1] == 'q') || (index >= (BLE_NUS_MAX_DATA_LEN - 1)))
??? {
??????? err_code =?ble_nus_send_string(&m_nus, data_array, index + 1);
??????? if (err_code != NRF_ERROR_INVALID_STATE)
??????? {
??????????? APP_ERROR_CHECK(err_code);
??????? }
//發(fā)送了數(shù)據(jù)后清零數(shù)組下標(biāo)。以繼續(xù)緩存后續(xù)的串口數(shù)據(jù)。
??????? index = 0;
??? }
}
?
再來(lái)看看發(fā)送數(shù)據(jù)給手機(jī)的函數(shù)ble_nus_send_string
?
uint32_t ble_nus_send_string(ble_nus_t * p_nus, uint8_t * string, uint16_t length)
{
??? ble_gatts_hvx_params_t hvx_params;
?
??? if (p_nus == NULL)
??? {
??????? return NRF_ERROR_NULL;
??? }
//這里是檢測(cè)參數(shù)是否正確。是否是已經(jīng)連接上了手機(jī)?(只有連接后,conn_handle才會(huì)
?? //?被賦值為有效值),檢查手機(jī)是否使能了開(kāi)發(fā)板的通知,因?yàn)殚_(kāi)發(fā)板作為服務(wù)端向手機(jī)
???????? //發(fā)送數(shù)據(jù)時(shí)通過(guò)通知或指示兩種方式,這兩種方式都需要手機(jī)先使能開(kāi)發(fā)板。
?? if((p_nus->conn_handle==BLE_CONN_HANDLE_INVALID)||(!p_nus->is_notification_enabled))
??? {
??????? return NRF_ERROR_INVALID_STATE;
??? }
???????? //一次發(fā)送的長(zhǎng)度不能超過(guò)限定值20
??? if (length > BLE_NUS_MAX_DATA_LEN)
??? {
??????? return NRF_ERROR_INVALID_PARAM;
??? }
?
??? memset(&hvx_params, 0, sizeof(hvx_params));
????????
???????? //以為是通過(guò)Rx這個(gè)參數(shù)來(lái)發(fā)送數(shù)據(jù)給手機(jī)的,所以句柄要填rx的句柄
???????? //這個(gè)句柄是在上面的服務(wù)初始化函數(shù)中的添加特征值函數(shù)調(diào)用完畢后或得的(最后一???? //?個(gè)參數(shù)為返回的句柄)
???????? //然后就是賦值要發(fā)送的數(shù)據(jù),并且設(shè)置為notify方式
??? hvx_params.handle = p_nus->rx_handles.value_handle;
??? hvx_params.p_data = string;
??? hvx_params.p_len? = &length;
??? hvx_params.type?? = BLE_GATT_HVX_NOTIFICATION;
//發(fā)送函數(shù)
??? return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);
}
?
?
?
看到這里應(yīng)該對(duì)電腦-》板子-》手機(jī)的數(shù)據(jù)流有一個(gè)認(rèn)識(shí)。在討論另一個(gè)方向的數(shù)據(jù)傳輸過(guò)程。我們先來(lái)看一個(gè)關(guān)于連接的問(wèn)題。
我們調(diào)用sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);發(fā)送數(shù)據(jù)給手機(jī)的時(shí)候,第二個(gè)參數(shù)是上面賦值的,那第一個(gè)參數(shù)這個(gè)連接句柄是怎么回事?在哪里設(shè)置過(guò)他?
連接句柄你可以看做是信道標(biāo)志一樣(實(shí)際數(shù)據(jù)接入地址),每?jī)蓚€(gè)連接的設(shè)備都會(huì)具有這個(gè)連接句柄。他們后續(xù)的通信都是通過(guò)這個(gè)連接句柄來(lái)進(jìn)行(可以理解是信道標(biāo)志,兩個(gè)設(shè)備的通信標(biāo)志必須一樣,這代表他們是在同樣的信道上通信才能正確進(jìn)行通信)
?
上面我們說(shuō)過(guò),板子整個(gè)初始化流程走完后就是睡眠和廣播等待手機(jī)連接。那么這個(gè)conn_handle就一定是手機(jī)發(fā)來(lái)連接,板子中協(xié)議棧處理完后上拋給app的連接事件中賦值的。從而記錄下后續(xù)板子和手機(jī)通信的”信道”。 在?程序框架剖析??那一講中介紹過(guò),協(xié)議棧拋上來(lái)的事件結(jié)構(gòu)體最終是由dispatch這個(gè)派發(fā)程序發(fā)給再發(fā)給各個(gè)服務(wù)的事件處理函數(shù)和模塊的事件處理函數(shù)的。
?
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
???????? //將事件交給連接管理模塊的事件處理函數(shù)
ble_conn_params_on_ble_evt(p_ble_evt);
//將事件交給uart服務(wù)的事件處理函數(shù)
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
//處理一些一般的事件
??? on_ble_evt(p_ble_evt);
}
再進(jìn)入?uart服務(wù)的事件處理函數(shù)中看下發(fā)生連接時(shí)是如何記下 后續(xù)通信所用的連接句柄的
這里只截取部分相關(guān)代碼
?
說(shuō)完了連接句柄下面來(lái)說(shuō)最后一個(gè)問(wèn)題
手機(jī)-》板子—》電腦方向的數(shù)據(jù)處理過(guò)程。
四:接收手機(jī)數(shù)據(jù)并通過(guò)串口打印:
其實(shí)看完了上面關(guān)于連接句柄的記錄。再來(lái)理解怎么收到手機(jī)的數(shù)據(jù)就容易了。
因?yàn)槲覀冋f(shuō)過(guò),手機(jī)發(fā)送數(shù)據(jù)過(guò)來(lái)也是一個(gè)事件!
既然都是事件,那么傳遞流程一定是一樣的,只是在最后的處理上不同的事件不同的處理。
?
那么第一步一定是協(xié)議棧處理完收到的數(shù)據(jù)打包一個(gè)?“寫(xiě)事件”?然后上拋給app。其實(shí)就是上拋給dispatch。然后在由它繼續(xù)分發(fā)事件
?
再進(jìn)入函數(shù)內(nèi)部:
再進(jìn)入on_write函數(shù)內(nèi)部一看究竟。
這里最終是調(diào)用了一個(gè)回調(diào)函數(shù)來(lái)數(shù)理最終的數(shù)據(jù),那么這個(gè)回調(diào)函數(shù)是什么時(shí)候注冊(cè)的。在 第二部分 函數(shù)單獨(dú)解析的?services_init講解中說(shuō)明過(guò)。
再來(lái)看看注冊(cè)的這個(gè)?nus_data_handler?到底干了什么
?
到這里手機(jī)->板子->電腦方向的數(shù)據(jù)流也理清了
整體的流程就是 手機(jī)發(fā)送數(shù)據(jù)給板子后,板子中低層的協(xié)議棧將收到的數(shù)據(jù)打包成一個(gè)寫(xiě)事件結(jié)構(gòu)體,然后上拋給app,最終由app種的diapatch再分發(fā)給各個(gè)服務(wù)或模塊的事件處理函數(shù),而uart的事件處理函數(shù)收到寫(xiě)事件后判斷是不是要打印到電腦上的”普通數(shù)據(jù)”,如果是就調(diào)用server_init中注冊(cè)的回調(diào)函數(shù)。該回調(diào)函數(shù)最終將數(shù)據(jù)打印到電腦上
總結(jié)
以上是生活随笔為你收集整理的BLE-NRF51822教程4-串口BLE解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BLE-NRF51822教程11-手机动
- 下一篇: BLE-NRF51822教程5-静态密码