在上一篇添加GATT Service的基础上,继续添加OTA。
nRF52840的代码区块可以分为三块:SoftDevice,Application,Bootloader。如图 1.1‑1所示:
图 1.1‑1 nRF52840 Flash分配
SoftDevice即BLE协议栈,只要用到蓝牙相关功能,则必须烧写。SoftDevice是Nordic提供的,需要注意其版本。例如S112,S113和S132是支持nRF52832,S140是支持-nRF52840的。一般来说应该使用最新版本的协议栈,但如果与SDK配合不一致的话,可能会出现问题,可以适当降低版本测试。例如本例使用SDK15.2与最新的协议栈S140 v7.0.1实验时会导致开发板跑不起来,而用S140 v6.1.1则没有问题。
Application即应用程序,用来实现主要功能。
BootLoader主要用于实现Device Firmware Update(DFU)。芯片在上电后,首先跑SoftDevice,然后查看是否存在BootLoader,如果没有则去跑Application,如果有则先去跑BootLoader,在BootLoader中会检查Application是否有更新,如果有更新则将新固件搬移倒Application区域。
这里涉及到Dual Bank的升级模式,在Application区域有两个bank,如图 1.1‑2所示,一个正常跑的Application区域,一个用于DFU的swap区域。当有在DFU模式时,先往Swap区域中写入新的Firmware,当重启时进入BootLoader,将Swap中的Image复制到Application中,执行新的程序。
图 1.1‑2 Dual Bank模式
nordic默认的DFU服务UUID为0xFE59,包含两个characteristic。一个是control point,其subUUID是0x0001;另一个是packet characteristic,其subUUID是0x0002。
DFU的过程如图所示,主机在DFU连接成功后,首先使能DFUctrlPoint的Notification,之后告知设备要开始DFU了,请做好准备。然后将新固件的大小告知设备,设备会检查是否有足够空间,如果有则准备好并通知主机。主机收到通知后会向设备发送Init Packet,在Init Packet中会包含设备类型,固件版本号,CRC校验码等信息,设备收到后会检查对应的信息是否满足更新条件,如果可以更新则通知主机可以开始发送固件了。设备通过Packet characteristic开始并不断接收新的固件,直到主机通过Ctrl Point向设备发送Validate Firmware,如果设备检查完CRC发现没有问题,则回复主机正常。之后设备会重启进入BootLoader,将新的固件搬移到Application区域,并跳转过去。
图 3.1‑1 DFU流程
这里直接使用SDK自带的secure_bootloader, 首先确保python版本为2.7,且nrfutil安装在python2.7下。
1.在路径/examples/dfu下, shift+右键打开cmd,键入
nrfutil keys generate private.key
得到私钥private.key
2.键入
nrfutil keys display --key pk --format code private.key --out_file public_key.c
得到公钥‘public_key.c’
3.复制micro-ecc文件夹到/external/micro-ecc下
4.进入nrf52hf_keil/armgcc,在此处打开cmd,make编译一下。由于这里使用的是mingW,所以这里的命令是mingw32-make。
5.在\examples\dfu\secure_bootloader\pca10056_ble_debug\arm5_no_packs下打开工程,用刚生成的‘public_key.c’替换掉工程中原有的公钥,编译工程,得到’nrf52840_xxaa_s140.hex’
这里添加的是buttonless_dfu。
1. 首先添加相关文件:
1) 在nRF_BLE中添加:components\ble\peer_manager下所有.c文件
2) 在nRF_DFU中添加:ble_dfu.c, ble_dfu_bounded.c, ble_dfu_unbounded.c
3) 在nRF_SVC中添加:nrf_dfu_svc.c
4) 在sdk_config中使能BLE_DFU_ENABLE
2. 在main中添加相关头文件
#include "ble_dfu.h"
#include "nrf_bootloader_info.h"
#include "nrf_power.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_fstorage.h"
#include "fds.h"
#include "ble_conn_state.h"
3. 在service_init()中初始化dfu service
ble_dfu_buttonless_init_t dfus_init = {0};
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
4. 从buttonless_dfu例程中拷贝以下代码到我们的程序中
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
switch (event)
{
case NRF_PWR_MGMT_EVT_PREPARE_DFU:
NRF_LOG_INFO("Power management wants to reset to DFU mode.");
break;
default:
return true;
}
NRF_LOG_INFO("Power management allowed to reset to DFU mode.");
return true;
}
//lint -esym(528, m_app_shutdown_handler)
/**@brief Register application shutdown handler with priority 0.
*/
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);
static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context)
{
if (state == NRF_SDH_EVT_STATE_DISABLED)
{
// Softdevice was disabled before going into reset. Inform bootloader to skip CRC on next boot.
nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);
//Go to system off.
nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
}
}
/* nrf_sdh state observer. */
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
.handler = buttonless_dfu_sdh_state_observer,
};
static void advertising_config_get(ble_adv_modes_config_t * p_config)
{
memset(p_config, 0, sizeof(ble_adv_modes_config_t));
p_config->ble_adv_fast_enabled = true;
p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
p_config->ble_adv_fast_timeout = APP_ADV_DURATION;
}
static void disconnect(uint16_t conn_handle, void * p_context)
{
UNUSED_PARAMETER(p_context);
ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
if (err_code != NRF_SUCCESS)
{
NRF_LOG_WARNING("Failed to disconnect connection. Connection handle: %d Error: %d", conn_handle, err_code);
}
else
{
NRF_LOG_DEBUG("Disconnected connection handle %d", conn_handle);
}
}
// YOUR_JOB: Update this code if you want to do anything given a DFU event (optional).
/**@brief Function for handling dfu events from the Buttonless Secure DFU service
*
* @param[in] event Event from the Buttonless Secure DFU service.
*/
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
NRF_LOG_INFO("ble_dfu_evt_handler,evt:%d",event);
switch (event)
{
case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
{
NRF_LOG_INFO("Device is preparing to enter bootloader mode.");
// Prevent device from advertising on disconnect.
ble_adv_modes_config_t config;
advertising_config_get(&config);
config.ble_adv_on_disconnect_disabled = true;
ble_advertising_modes_config_set(&m_advertising, &config);
// Disconnect all other bonded devices that currently are connected.
// This is required to receive a service changed indication
// on bootup after a successful (or aborted) Device Firmware Update.
uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
NRF_LOG_INFO("Disconnected %d links.", conn_count);
break;
}
case BLE_DFU_EVT_BOOTLOADER_ENTER:
// YOUR_JOB: Write app-specific unwritten data to FLASH, control finalization of this
// by delaying reset by reporting false in app_shutdown_handler
NRF_LOG_INFO("Device will enter bootloader mode.");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
NRF_LOG_ERROR("Request to enter bootloader mode failed asynchroneously.");
// YOUR_JOB: Take corrective measures to resolve the issue
// like calling APP_ERROR_CHECK to reset the device.
break;
case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
NRF_LOG_ERROR("Request to send a response to client failed.");
// YOUR_JOB: Take corrective measures to resolve the issue
// like calling APP_ERROR_CHECK to reset the device.
APP_ERROR_CHECK(false);
break;
default:
NRF_LOG_ERROR("Unknown event from ble_dfu_buttonless.");
break;
}
}
/**@brief Function for handling Peer Manager events.
*
* @param[in] p_evt Peer Manager event.
*/
static void pm_evt_handler(pm_evt_t const * p_evt)
{
pm_handler_on_pm_evt(p_evt);
pm_handler_flash_clean(p_evt);
}
5. 在main()中添加
// Initialize the async SVCI interface to bootloader before any interrupts are enabled.
err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);
6.因为增加了一个GATT service,所以需要告知SDK多申请一部分RAM来处理。在sdk_config.h中,将NRF_SDH_BLE_VS_UUID_COUNT的值改为2
#ifndef NRF_SDH_BLE_VS_UUID_COUNT
#define NRF_SDH_BLE_VS_UUID_COUNT 2
#endif
7. 编译,解决掉剩下的error。编译成功后,得到‘nrf52_ble_sean_service.hex’。
将之前得到的‘private.key’和‘nrf52_ble_sean_service.hex’放到同一个文件夹下,打开cmd,键入
nrfutil settings generate –-family NRF52840 –-application nrf52_ble_sean_service.hex –-application-version 1 –-bootloader-version 0 –-ble-settings-version 1 bootyloader_settings.hex
将我们原有的软件更改设备名为SEAN_SERVICE_3,编译后用以下指令打包
nrfutil pkg generate --hw-version 52 --application-version 3 --application nrf52_ble_sean_service.hex --sd-req 0xB6 --key-file private.key sean_dfu_package.zip
首先我们先烧写未改名的固件,如图所示打开nRF connect -> Programmer,在左上角连接好板子,然后依次将
拖入右侧区域,点击‘Erase&write’将所有程序烧写进板子。
图 3.6‑1 烧写固件
在手机端nRF Connect上找到我们的开发板’SEAN SERVICE’,点击’’connet’,在’Service’页面可以看到我们已经添加好的’DFU Service’,如图 3.6‑2所示。
图 3.6‑2 DFU Servicce
向左滑动进入’’DFU’页面,找到我们打包好的’’‘sean_dfu_service.zip’,如图 3.6‑3所示。
图 3.6‑3 找到新固件
然后等待一段更新的动画,如图 3.6‑4所示。
图 3.6‑4 更新动画
最后页面会显示DFU更新成功,如图 3.6‑5所示,可以搜索到新名称的板子。
图 3.6‑5 更新成功