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. 添加工程文件
- 在Application中添加main.c , sdk_config.h
- 在Board Definition中添加boards.c
- 在Board Support中添加bsp.c, bsp_btn_ble.c
- 在UTF8/UTF16 converter中添加utf.c
- 在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
- 在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.
- 在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
- 在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
- 在nRF_Segger_RTT中添加SEGGER_RTT.c, SEGGER_RTT_Syscalls_KEIL.c, SEGGER_RTT_printf.c
- 在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, ®ister_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所示,可以读到陀螺仪的位置数据,且可以看到数据在实时更新。