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所示,可以讀到陀螺儀的位置數據,且可以看到數據在實時更新。