【Nordic】52840 搭建GATT Service

1 NRF52840 SDK 基礎

1.1 GATT Service

藍牙協議中定義 GATT service 爲:

“A service is a collection of data and associated behaviors to accomplish a particular function or feature. [...] A service definition may contain […] mandatory characteristics and optional characteristics.”

可以簡單理解爲爲了實現一定的功能,將所用到的數據和動作集合成一個整體。藍牙協議已定義好一些固定的服務,例如 0x1800 Generic Access, 0x180F Battery Service,0x180A Device Informati0on, 0x180D Heart Rate 等。

1.2 Characteristic

藍牙協議中定義 Characteristic 爲:

“A characteristic is a value used in a service along with properties and configuration information about how the value is accessed and information about how the value is displayed or represented.”

圖 1.2-1 GATT 服務和特徵關係

在服務的基礎上,可以理解特徵爲實際承載和傳輸信息的地方。服務、特徵和 UUID 的關係可以簡單理解爲圖 1.2-1 的關係,儲藏室內有一個文件櫃,文件櫃有三個抽屜,每個抽屜上有各自的標號,那麼這個文件櫃可以看做是服務,抽屜可以看做是特徵,而抽屜上的標號可以看做是 UUID,信息就是抽屜裏的文件。

1.3 讀寫流程

nrf52840 的 GATT Service 服務讀寫流程有較大不同。如圖 1.3-1 所示:

圖 1.3-1 GATT Service 讀寫流程

Read:外部設備通過 GATT Service 發過來的信息,被協議棧打包,通過事件的形式傳遞給應用層,在應用層需要調用 observe 函數來監控各事件,在從其中解析出需要的數據。


Write:在應用層將需要發送的數據打包,調用協議棧函數 sd_ble_gatts_hvx()傳遞給協議棧,協議棧會根據其收到的參數來選擇通過哪一個服務的哪一個特徵,以什麼形式發送出去。

1.4 設備和軟件

設備:nRF52840 開發板,PCA10056
SDK:nRF5_SDK_15.2.0
SoftDevice:s140_nrf52_6.1.1

IDE: Keil5.21 + SourceInsight4.0
PC 端燒錄工具 nRF Connect + nrfjprog
手機端測試工具:nRF Connect

 

2 新建和配置工程

1. 新建工程,設備選擇 nRF52840 嗎,如下圖所示。

圖 1.4‑1 新建工程

2. 選擇擴展包,選擇CMSIS-CORE, Device-Startup,如下圖所示。

 

圖 1.4‑2 選擇擴展包

3. 在工程目錄example/ble_peripheral/ble_app_sean下新建文件夾config,存放配置文件,複製一份sdk_config.h 到工程目錄下,如下圖所示。

圖 1.4‑3 準備sdk_config

爲了在調試時可以從串口看到調試NRF_LOG_INFO的信息,確保在sdk_config.h中有添加NRF_LOG_BACKEND_UART_ENABLED。

// <e> NRF_LOG_BACKEND_UART_ENABLED - nrf_log_backend_uart - Log UART backend
//==========================================================
#ifndef NRF_LOG_BACKEND_UART_ENABLED
#define NRF_LOG_BACKEND_UART_ENABLED 1
#endif
// <o> NRF_LOG_BACKEND_UART_TX_PIN - UART TX pin 
#ifndef NRF_LOG_BACKEND_UART_TX_PIN
#define NRF_LOG_BACKEND_UART_TX_PIN 6
#endif

// <o> NRF_LOG_BACKEND_UART_BAUDRATE  - Default Baudrate
 
// <323584=> 1200 baud 
// <643072=> 2400 baud 
// <1290240=> 4800 baud 
// <2576384=> 9600 baud 
// <3862528=> 14400 baud 
// <5152768=> 19200 baud 
// <7716864=> 28800 baud 
// <10289152=> 38400 baud 
// <15400960=> 57600 baud 
// <20615168=> 76800 baud 
// <30801920=> 115200 baud 
// <61865984=> 230400 baud 
// <67108864=> 250000 baud 
// <121634816=> 460800 baud 
// <251658240=> 921600 baud 
// <268435456=> 1000000 baud 

#ifndef NRF_LOG_BACKEND_UART_BAUDRATE
#define NRF_LOG_BACKEND_UART_BAUDRATE 30801920
#endif

// <o> NRF_LOG_BACKEND_UART_TEMP_BUFFER_SIZE - Size of buffer for partially processed strings. 
// <i> Size of the buffer is a trade-off between RAM usage and processing.
// <i> if buffer is smaller then strings will often be fragmented.
// <i> It is recommended to use size which will fit typical log and only the
// <i> longer one will be fragmented.

#ifndef NRF_LOG_BACKEND_UART_TEMP_BUFFER_SIZE
#define NRF_LOG_BACKEND_UART_TEMP_BUFFER_SIZE 64
#endif

4. 添加工程文件

  1. 在Application中添加main.c , sdk_config.h
  2. 在Board Definition中添加boards.c
  3. 在Board Support中添加bsp.c, bsp_btn_ble.c
  4. 在UTF8/UTF16 converter中添加utf.c
  5. 在nRF_BLE中添加ble_advdata.c, ble_advertisng.c, ble_conn_params.c, ble_conn_state.c, ble_link_ctx_manager.c, ble_srv_common.c, nrf_ble_gatt.c, nrf_ble_qwr.c
  6. 在nRF_Drivers中添加nrf_drv_clock.c, nrf_drv_uart.c, nrf_clock.c, nrfx_gpiote.c, nrfx_power_clock.c, nrfx_prs.c, nrfx_uart.c, nrfx_uarte.c.
  7. 在nRF_Libraries中添加 app_button.c, app_error.c, app_error_handle_keil.c, app_error_weak.c, app_util_platform.c, app_scheduler.c, app_timer.c, crc16.c, fds.c, hardfault_implementation.c, nrf_assert.c, nrf_atfifo.c, nrf_atflags.c nrf_atomic.c, nrf_balloc.c, nrf_fprintf.c, nrf_fprintf_format.c, nrf_fstorage.c, nrf_fstroage_sd.c, nrf_pwr_mgmt.c, nrf_section_iter.c, nrf_strerror.c, sensorsim.c. nrf_memobj.c, nrf_ringbuf.c
  8. 在nRF_Log中添加:nrf_log_backend_rtt.c, nrf_log_backend_serial.c, nrf_log_default_backends.c, nrf_log_frontend.c, nrf_log_str_formatter.c
  9. 在nRF_Segger_RTT中添加SEGGER_RTT.c, SEGGER_RTT_Syscalls_KEIL.c, SEGGER_RTT_printf.c
  10. 在nRF_SoftDevice中添加nrf_sdh.c, nrf_sdh_ble.c, nrf_sdh_soc.h

 5. 在Option for Target ‘nrf52840_sean’中,配置

Target:

圖 1.4‑4 配置工程Target

Output:

圖 1.4‑5 配置工程Output

C/C++: 在Define寫:BOARD_PCA10056 CONFIG_GPIO_AS_PINRESET FLOAT_ABI_HARD NRF52840_XXAA NRF_SD_BLE_API_VERSION=6 S140 SOFTDEVICE_PRESENT SWI_DISABLE0 __HEAP_SIZE=8192 __STACK_SIZE=8192

其他選項按下圖配置:

Asm:

Linker:

圖 1.4‑8 配置工程Linker

Debug: 選J-Link

圖 1.4‑9 配置工程Debug

6. 點擊ok。在main.c中寫

int main(void)
{
	return 0;
}

編譯成功,配置工程結束。

3 添加代碼

3.1添加基礎代碼

在main中,添加相關頭文件和宏定義

#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "ble_conn_params.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"
#include "nrf_ble_qwr.h"
#include "app_timer.h"
#include "app_uart.h"
#include "app_util_platform.h"
#include "bsp_btn_ble.h"
#include "nrf_pwr_mgmt.h"
#include "sean_service.h"
#include "accel_mpu6050.h"

#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"


#define APP_BLE_CONN_CFG_TAG 1  /**< A tag identifying the SoftDevice BLE configuration. */
#define DEVICE_NAME                     "SEAN SERVICE"                               /**< Name of device. Will be included in the advertising data. */
#define APP_BLE_OBSERVER_PRIO 3                                           /**< Application's BLE observer priority. You shouldn't need to modify this value. */
#define APP_ADV_INTERVAL 64                                          /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */
#define APP_ADV_DURATION 18000                                       /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(75, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
#define SLAVE_LATENCY                   0                                           /**< Slave latency. */
#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
#define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           /**< Number of attempts before giving up the connection parameter negotiation. */
#define DEAD_BEEF                       0xDEADBEEF                                  /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

 

3.2添加服務sean service

STEP3.2.A 在sean_service.h中創建一個ble_os_t類型存放服務相關的數據和信息,並聲明一個該類型的變量。

typedef struct
{
	uint16_t conn_handle;
	uint16_t service_handle;     /**< Handle of Our Service (as provided by the BLE stack). */
}ble_os_t;

ble_os_t m_sean_service;

 STEP3.2.B 在service_init()中添加our_service_init(),並傳遞剛聲明的m_sean_service。

/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
uint32_t           err_code;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    our_service_init(&m_our_service);
}

STEP3.2.C 在our_service_init()中,我們首先創建服務需要的UUID。這裏需要一個16bit的服務UUID,和一個128bit的baseUUID。

uint32_t   err_code;
ble_uuid_t        service_uuid;
ble_uuid128_t     base_uuid = BLE_UUID_OUR_BASE_UUID;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
APP_ERROR_CHECK(err_code);

在sean_service.h中添加兩個UUID的定義

#define BLE_UUID_SEAN_BASE_UUID {0x24, 0xD1, 0x13, 0xEF, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00} // 128-bit base UUID
#define BLE_UUID_SEAN_SERVICE 0xABCD // Just a random, but recognizable value

STEP3.2.D 在our_service_init()中調用sd_ble_gatts_service_add註冊一個服務,

err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                    &service_uuid,
                                    &p_sean_service->service_handle);
APP_ERROR_CHECK(err_code);

STEP3.2.E 創建一個保存service UUID的變量

static ble_uuid_t m_adv_uuids[] = 
{
    {
        BLE_UUID_OUR_SERVICE,
        BLE_UUID_TYPE_VENDOR_BEGIN
    }
};

STEP3.2.F 在advertising_init()中添加剛聲明的變量

init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids  = m_adv_uuids;

3.3 添加第一個characteristic

STEP3.3.A 在sean_service.c中添加函數sean_char_add(),並在其中創建characteristic的UUID

uint32_t sean_char_add(ble_os_t * p_sean_service)
{
	uint32_t			err_code;
	ble_uuid_t		char_uuid;
	ble_uuid128_t		base_uuid = BLE_UUID_SEAN_BASE_UUID;
	char_uuid.uuid		= BLE_UUID_OUR_CHARACTERISTC_UUID;
	err_code = sd_ble_uuid_vs_add(&base_uuid, &char_uuid.type);
	APP_ERROR_CHECK(err_code);	
}

在sean_service.h中定義BLE_UUID_OUR_CHARACTERISTC_UUID爲0xBEEF。

STEP3.3.B 在sean_char_add()中繼續添加,創建並配置attr_md

ble_gatts_attr_md_t attr_md;
memset(&attr_md, 0, sizeof(attr_md));
attr_md.vloc = BLE_GATTS_VLOC_STACK;

STEP3.3.C 在sean_char_add()中繼續添加,創建並配置attr_char_value

ble_gatts_attr_t    attr_char_value;
memset(&attr_char_value, 0, sizeof(attr_char_value));    
attr_char_value.p_uuid      = &char_uuid;
attr_char_value.p_attr_md   = &attr_md;

STEP3.3.D 在ble_os_t中添加characteristic的句柄,添加完成後,ble_os_t如下

typedef struct
{
    uint16_t                    conn_handle; 
    uint16_t                    service_handle;        
    ble_gatts_char_handles_t    char_handles;
}ble_os_t;

STEP3.3.E 在sean_char_add()中定義char_md,來配置characteristic的讀寫權限

ble_gatts_char_md_t char_md;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.read = 1;
char_md.char_props.write = 1;

STEP3.3.F 在sean_char_add()中添加特徵

err_code = sd_ble_gatts_characteristic_add(p_sean_service->service_handle,
					&char_md,
					&attr_char_value,
					&p_sean_service->char_handles);
APP_ERROR_CHECK(err_code);

STEP3.3.G 在sean_char_add()中添加特徵,開啓特徵的讀寫權限

BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);

STEP3.3.H 在sean_char_add()中設置讀取的值

attr_char_value.max_len = 4;
attr_char_value.init_len = 4;
uint8_t value[4] = {0x12, 0x34, 0x56, 0x78};
attr_char_value.p_value = value

STEP3.3.I 在sean_char_add()中定義cccd_md

ble_gatts_attr_md_t cccd_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
char_md.p_cccd_md = &cccd_md;
char_md.char_props.notify = 1; 

STEP3.3.J 在our_service_init中初始化conn_handle

p_sean_service->conn_handle = BLE_CONN_HANDLE_INVALID;

STEP3.3.K 在ble_stack_init中註冊一個事件響應函數,來響應連接和斷開等事件

NRF_SDH_BLE_OBSERVER(m_sean_service_observer, APP_BLE_OBSERVER_PRIO, ble_sean_service_on_ble_evt, (void*)&m_sean_service);

STEP3.3.L 創建ble_our_service_on_ble_evt函數來響應連接和斷開事件:

void ble_sean_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
  	ble_os_t * p_sean_service =(ble_os_t *) p_context;  
		// OUR_JOB: Step 3.D Implement switch case handling BLE events related to our service. 
	switch(p_ble_evt->header.evt_id)
	{
		case BLE_GAP_EVT_CONNECTED:
			p_sean_service->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
			break;
		case BLE_GAP_EVT_DISCONNECTED:
			p_sean_service->conn_handle = BLE_CONN_HANDLE_INVALID;
			break;
		default:
			break;
	}
}

STEP3.3.M 在sean_service.c中創建our_temperature_characteristic_update函數來更新溫度值,並在sean_service.h中聲明。

void our_temperature_characteristic_update(ble_os_t *p_our_service, int32_t *temperature_value)
{
	 if(p_our_service->conn_handle != BLE_CONN_HANDLE_INVALID)
	 {
		 uint16_t len = 4;
		 ble_gatts_hvx_params_t hvx_params;
		 memset(&hvx_params, 0, sizeof(hvx_params));
		 hvx_params.handle = p_our_service->char_handles.value_handle;
		 hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
		 hvx_params.offset = 0;
		 hvx_params.p_len = &len;
		 hvx_params.p_data = (uint8_t*)temperature_value;
		 
		 sd_ble_gatts_hvx(p_our_service->conn_handle, &hvx_params);
		 NRF_LOG_INFO("temperature:%d", *temperature_value);
	 }
}

STEP3.3.N 在sean_service.c中創建timer_timeout_handler來響應定時器

void timer_timeout_handler(void * p_context)
{

}

STEP3.3.N 在sean_service.h中定義一個定時器,並設置觸發間隔爲1s

APP_TIMER_DEF(m_sean_char_timer_id);
#define OUR_CHAR_TIMER_INTERVAL APP_TIMER_TICKS(1000) //1000ms intervals

STEP3.3.O 在timers_init中初始化該定時器

app_timer_create(&m_sean_char_timer_id, APP_TIMER_MODE_REPEATED, timer_timeout_handler);

STEP3.3.P 創建函數application_timers_start啓動定時器,並在main中調用

static void application_timers_start(void)
{
	app_timer_start(m_sean_char_timer_id, OUR_CHAR_TIMER_INTERVAL, NULL);
}

STEP3.3.Q 在ble_sean_service_on_ble_evt函數中添加對事件BLE_GATTS_EVT_WRITE的處理

case BLE_GATTS_EVT_WRITE:
	on_write_value(p_sean_service, p_ble_evt);
	break;

在sean_service.c中創建函數on_write_value來處理接收到的數據,並在sean_service.h中聲明。

void on_write_value(ble_os_t *p_sean_service, ble_evt_t const * p_ble_evt)
{
	ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
	uint8_t value[4];
	memcpy(value, p_evt_write->data, p_evt_write->len);
	NRF_LOG_INFO("write value:%x", value);
}

STEP3.3.R 在sean_char_add中添加

attr_md.rd_auth = 1;

來允許協議棧將受到的read request實踐返回到應用層,在應用層可以通過事件BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST來監控。

在ble_sean_service_on_ble_evt函數中添加對事件BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST的處理。

case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST:
	on_read_value(p_sean_service, p_ble_evt);
	break;

在sean_service.c中創建函數on_read_value來處理接收到的數據,並在sean_service.h中聲明。

void on_read_value(ble_os_t *p_sean_service, ble_evt_t const * p_ble_evt)
{
	if(p_ble_evt->evt.gatts_evt.params.authorize_request.type == BLE_GATTS_AUTHORIZE_TYPE_READ)
	{
		NRF_LOG_INFO("on read value");
		uint32_t err_code;
		int32_t temperature;
		ble_gatts_rw_authorize_reply_params_t reply;
		memset(&reply, 0, sizeof(reply));
	
		sd_temp_get(&temperature);
		reply.type = BLE_GATTS_AUTHORIZE_TYPE_READ;
		reply.params.read.gatt_status = BLE_GATT_STATUS_SUCCESS;
		err_code = sd_ble_gatts_rw_authorize_reply(p_sean_service->conn_handle, &reply);
		APP_ERROR_CHECK(err_code);

		sean_temperature_characteristic_update(&m_sean_service, &temperature);
	}
}

3.4添加ACCEL_SERVICE

BLE的每一個服務往往具備多個特徵,在成功實現了一個特徵之後,本節在該服務中繼續添加第二個特徵,來實現一個加速度傳感器的notification特性。將nrf52840通過TWI讀取加速度傳感器MPU6050的XYZ數值,然後在定時器中將該數值以notification特性發送給手機。

我們首先來實現TWI,完成對MPU6050的初始化。

STEP3.4.A 首先添加HAL層文件和DRV層文件:nrfx_twi.c, nrfx_drv_twi.c, twi_sw_master.c, nrfx_twim.c

STEP3.4.B修改sdk_config.h的配置

一定要注意這兩步,將twi相關的文件添加到工程中,將相關配置使能,後續如果出現undefined symble等編譯或鏈接錯誤,大概率是缺少頭文件和配置。

STEP3.4.C 定義一個twi實例

#define TWI_INSTANCE_ID     0
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

STEP3.4.D 添加函數twi_init()來初始化twi模塊,將SCL和SDA映射到兩個引腳上,設置頻率和中斷優先級,將這些參數傳遞給nrf_drv_twi_init()來完成初始化。

void twi_init (void)
{
    ret_code_t err_code;
	
	const nrf_drv_twi_config_t twi_mpu6050_config = {
       .scl                = DK_SCL_PIN,
       .sda                = DK_SDA_PIN,
       .frequency          = NRF_DRV_TWI_FREQ_100K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false 
	};

	err_code = nrf_drv_twi_init(&m_twi, &twi_mpu6050_config, NULL, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);	
}

STEP3.4.E 通過twi發送數據的接口是nrf_drv_twi_tx()

__STATIC_INLINE
ret_code_t nrf_drv_twi_tx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t const *       p_data,
                          uint8_t               length,
                          bool                  no_stop);

p_instance是第三步定義的實例,address是設備地址,no_stop是選擇是否有停止位。

STEP3.4.F 通過twi接收數據的接口是nrf_drv_twi_rx()

__STATIC_INLINE
ret_code_t nrf_drv_twi_rx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t *             p_data,
                          uint8_t               length);

STEP3.4.G 實現對mpu6050的讀寫函數

根據”MPU-6000-Datasheet”中對讀寫時序的描述,實現讀寫寄存器的函數。

圖 3.4‑1 MPU6050 I2C寫時序

 

圖 3.4‑2 MPU6050 I2C讀時序

ret_code_t MPU6050_register_write(uint8_t register_address, uint8_t value)
{
	ret_code_t err_code;
    uint8_t w2_data[2];
    w2_data[0] = register_address;
    w2_data[1] = value;
err_code = nrf_drv_twi_tx(&m_twi, mpu6050_device_address, w2_data, sizeof(w2_data), false);
    APP_ERROR_CHECK(err_code);
	return err_code;
}

ret_code_t MPU6050_register_read(uint8_t register_address, uint8_t *dest, uint8_t number_of_bytes)
{
	ret_code_t err_code;
	err_code = nrf_drv_twi_tx(&m_twi, mpu6050_device_address, &register_address, 1, true);
	APP_ERROR_CHECK(err_code);
	err_code = nrf_drv_twi_rx(&m_twi, mpu6050_device_address, dest, number_of_bytes);
	APP_ERROR_CHECK(err_code);
    return err_code;
}

STEP3.4.H 根據datasheet中對寄存器的描述,完成對MPU6050的初始化。當然首先要在accel_mpu6050.h中定義所用到的寄存器地址。

bool MPU6050_init(void)
{
    bool transfer_succeeded = true;
	uint8_t tmp = 0xFF;

    mpu6050_device_address = 0x68;

    // Read and verify product ID
    transfer_succeeded = MPU6050_verify_product_id();

	if(transfer_succeeded)
	{
		MPU6050_register_write(PWR_MGMT_1, 0x80);
		nrf_delay_ms(100);
		MPU6050_register_write(PWR_MGMT_1, 0x00);
		while(tmp)
		{
			MPU6050_register_read(PWR_MGMT_1, &tmp, 2);
		}
		transfer_succeeded = MPU6050_verify_product_id();

		MPU6050_register_write(SMPLRT_DIV, 0x07);
		MPU6050_register_write(CONFIG, 0x06);
		MPU6050_register_write(GYRO_CONFIG, 0x18);
		MPU6050_register_write(ACCEL_CONFIG, 0x18);
	}
    return transfer_succeeded;
}

STEP3.4.I 初始化時,可以通過讀寄存器ADDR_WHO_AM_I來判斷TWI總線是否通信正常,在aeecl_mpu6050.c中實現MPU6050_verify_product_id。

bool MPU6050_verify_product_id()
{

	uint8_t who_am_i;

    if (MPU6050_register_read(ADDR_WHO_AM_I, &who_am_i, 1) == NRF_SUCCESS)
    {
        if (who_am_i != e_who_am_i)
        {
             NRF_LOG_INFO("mpu6050 verify fail:%X", who_am_i);
		NRF_LOG_FLUSH();
             return false;
        }
        else
        {
        	NRF_LOG_INFO("mpu6050 verify pass.");
	NRF_LOG_FLUSH();
             return true;
        }
    }
    else
    {
    		NRF_LOG_INFO("mpu6050 read fail");
		NRF_LOG_FLUSH();
        		return false;
    }
}

STEP3.4.J 爲了方便保存從mpu6050讀到的數據,這裏定義一個結構體,並創建一個全局變量。

// accel_mpu6050.h
typedef struct mpu6050_pos
{
	int16_t gyro[3];
	int16_t accel[3];
	float temp;	//溫度
}mpu6050_pos_t;

// accel_mpu6050.c
mpu6050_pos_t g_mpu6050_pos;

STEP3.4.K 從datasheet中瞭解到,gyro和accel是以16位二進制補碼的形式存放的,所以在accel_mpu6050.c中實現一個補碼轉原碼的函數。

int16_t comp2raw(int16_t a)
{
	return a>=0?a:~(-a)+1;
}

STEP3.4.L 陀螺儀相關的配置基本完成,接下來在sean_service中添加第二個特徵gyro_characteristic,首先在sean_service.h中添加該特徵的UUID

#define BLE_UUID_GYRO_CHARACTERISTC_UUID 0xB00F

STEP3.4.M 在ble_os_t中添加第二個特徵的句柄,添加完成後如下

typedef struct
{
	uint16_t conn_handle;
	uint16_t service_handle;     	/**< Handle of Our Service (as provided by the BLE stack). */
	ble_gatts_char_handles_t char_handles;		/**< Handle of sean first characterstic*/
	ble_gatts_char_handles_t gyro_handles;		/**< Handle of sean gyro characterstic*/
}ble_os_t;

STEP3.4.N 在sean_service.c中複製一份sean_char_add, 改名爲gyro_char_add,修改UUID,char_handle和attr_char_value的配置,如下

uint32_t gyro_char_add(ble_os_t * p_sean_service)
{
	uint32_t			err_code;
	ble_uuid_t			char_uuid;
	ble_uuid128_t		base_uuid = BLE_UUID_SEAN_BASE_UUID;
	char_uuid.uuid		= BLE_UUID_GYRO_CHARACTERISTC_UUID;
	err_code = sd_ble_uuid_vs_add(&base_uuid, &char_uuid.type);
	APP_ERROR_CHECK(err_code);	

	ble_gatts_attr_md_t attr_md;
	memset(&attr_md, 0, sizeof(attr_md));
	attr_md.vloc = BLE_GATTS_VLOC_STACK;
	attr_md.rd_auth = 1;

	BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
	BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);

	ble_gatts_attr_t	attr_char_value;
	memset(&attr_char_value, 0, sizeof(attr_char_value));	 
	attr_char_value.p_uuid		= &char_uuid;
	attr_char_value.p_attr_md	= &attr_md;

	ble_gatts_char_md_t char_md;
	memset(&char_md, 0, sizeof(char_md));
	char_md.char_props.read = 1;
	char_md.char_props.write = 1;

	ble_gatts_attr_md_t cccd_md;
	memset(&cccd_md, 0, sizeof(cccd_md));
	BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
	BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
	cccd_md.vloc = BLE_GATTS_VLOC_STACK;
	char_md.p_cccd_md = &cccd_md;
	char_md.char_props.notify = 1;  

	attr_char_value.max_len = sizeof(mpu6050_pos_t);
	attr_char_value.init_len = sizeof(mpu6050_pos_t);
	uint8_t value[4] = {0x12, 0x34, 0x56, 0x78};
	attr_char_value.p_value = value;

	err_code = sd_ble_gatts_characteristic_add(p_sean_service->service_handle,
						&char_md,									&attr_char_value,								&p_sean_service->gyro_handles);
	APP_ERROR_CHECK(err_code);
	return NRF_SUCCESS;
}

STEP3.4.O 在service_init中調用該函數,如下:

static void services_init(void)
{
    uint32_t           err_code;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    sean_service_init(&m_sean_service);
sean_char_add(&m_sean_service);
gyro_char_add(&m_sean_service);
NRF_LOG_INFO("service_init done");
}

STEP3.4.P 在3.3節實現的定時器中,每1s發送一次陀螺儀當前的數據

void timer_timeout_handler(void * p_context)
{	
	NRF_LOG_INFO("alive");
	ble_gyro_status_change(&m_sean_service, &g_mpu6050_pos);
}

STEP3.4.Q 在accel_mpu6050中實現發送數據的函數ble_gyro_status_change 在函數ble_gyro_status_change中,依然使用sd_ble_gatts_hvx接口來發送數據,陀螺儀的位置數據是調用函數MPU6050_get_position來獲得的。

uint32_t ble_gyro_status_change(ble_os_t *p_sean_service, mpu6050_pos_t *gyro_pos)
{
	MPU6050_get_position();
	if(p_sean_service->conn_handle != BLE_CONN_HANDLE_INVALID)
	{
		uint16_t len = sizeof(gyro_pos);
		ble_gatts_hvx_params_t hvx_params;
		memset(&hvx_params, 0, sizeof(hvx_params));
		hvx_params.handle = p_sean_service->gyro_handles.value_handle;
		hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
		hvx_params.offset = 0;
		hvx_params.p_len = &len;
		hvx_params.p_data = (uint8_t*)gyro_pos;
		 
		sd_ble_gatts_hvx(p_sean_service->conn_handle, &hvx_params);
		NRF_LOG_INFO("gyro send");
	}
	return NRF_SUCCESS;
}

STEP3.4.R 在accel_mpu6050.c中實現函數MPU6050_get_position

void MPU6050_get_position(void)
{
	ret_code_t err_code;
	int16_t gyro[3];
	int16_t accel[3];
	float temp;	//溫度
	uint8_t tmp[2]; //臨時變量
	NRF_LOG_INFO("get current position");
	err_code = MPU6050_register_read(GYRO_XOUT_H, tmp, 2);
	gyro[0] = comp2raw((tmp[0]<<8) + tmp[1]);
	
	err_code &= MPU6050_register_read(GYRO_YOUT_H, tmp, 2);
	gyro[1] = comp2raw((tmp[0]<<8) + tmp[1]);
	
	err_code &= MPU6050_register_read(GYRO_ZOUT_H, tmp, 2);
	gyro[2] = comp2raw((tmp[0]<<8) + tmp[1]);
	
	err_code &= MPU6050_register_read(TEMP_OUT_H, tmp, 2);
	temp = comp2raw((tmp[0]<<8) + tmp[1])/340 + 36.53;
	
	err_code &= MPU6050_register_read(ACCEL_XOUT_H, tmp, 2);
	accel[0] = comp2raw((tmp[0]<<8) + tmp[1]);
	
	err_code &= MPU6050_register_read(ACCEL_YOUT_H, tmp, 2);
	accel[1] = comp2raw((tmp[0]<<8) + tmp[1]);
	
	err_code &= MPU6050_register_read(ACCEL_ZOUT_H, tmp, 2);
	accel[2] = comp2raw((tmp[0]<<8) + tmp[1]);

	if(err_code != NRF_SUCCESS)
	{
		NRF_LOG_INFO("get position fail!");
	}
	else
	{
		NRF_LOG_INFO("position AND temperature:");
		NRF_LOG_INFO("g_x:%d, g_y:%d, g_z:%d", gyro[0], gyro[1], gyro[2]);
		NRF_LOG_INFO("a_x:%d, a_y:%d, a_z:%d", accel[0], accel[1], accel[2]);
		NRF_LOG_INFO("temperature:%d", temp);
		NRF_LOG_INFO("=============================================================");
	}
	memcpy(g_mpu6050_pos.accel, accel, sizeof(accel));
	memcpy(g_mpu6050_pos.gyro, gyro, sizeof(gyro));
	g_mpu6050_pos.temp = temp;
}

這樣每1s,nrf52840就從mpu6050中獲取一次位置信息,並以notification的形式發送到手機端。

4 實現效果

使用手機端的nRF Connect來連接開發板,可以在掃描頁面找到我們的設備“SEAN_SERVICE”,如下圖所示,點擊Connect,完成連接。

圖 3.4‑1 nRF Connect 連接頁面

連接完成後向左滑動到“Service”頁面,可以看到添加的服務和特徵,如圖所示,UUID爲0000ABCD-x的我們添加的服務,UUID爲0000BEEF-x的是第一個特徵,UUID爲0000B00F-x的是第二個特徵,也就是獲取陀螺儀數據的特徵。

點擊第一個特徵下的下箭頭,如圖 3.4‑2所示,可以讀到開發板發送的溫度數據,爲0x61,約爲36.1°C。點擊上箭頭,可以向開發板發送數據。

         圖 3.4‑2 第一個特徵                            

3.4‑3 陀螺儀特徵

點擊第二個特徵下最右側的三個下箭頭的圖標,使能notification,如圖 3.4‑3所示,可以讀到陀螺儀的位置數據,且可以看到數據在實時更新。

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