【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所示,可以读到陀螺仪的位置数据,且可以看到数据在实时更新。

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值