开发环境: SDK9
以 ble_app_uart例子为基础,在其上添加dfu服务。
Sdk中的bootloader提供了两个方式来进入升级模式,一种是按键,另一种是手机点击升级。
在bootloader代码相关代码如下
如果是手机app通过点击图标直接升级,那么在app跳转到bootloader进行升级前就会设置 NRF_POWER->GPREGRET 这个寄存器的值。所以bootloader代码中开始就是判断这个寄存器中的值来判断是不是手机触发的进入DFU模式。
下面的代码会判断是否有 设备上的按键按下,如果在上电过程中按下按键也会进入DFU模式。
PS:无论是通过手机上的 DFU图标进入升级模式,还是通过设备上的按键进入升级模式都是需要 bootloader的, 两种方式只是进入升级模式 的方式不同而已。
首先打开ble_app_uart工程在keil中添加如下所需模块
然后添加一些DFU相关的处理代码
Main.c中添加
#ifdef BLE_DFU_APP_SUPPORT
#include "ble_dfu.h"
#include "dfu_app_handler.h"
#endif
修改IS_SRVC_CHANGED_CHARACT_PRESENT 宏为1
Main.c中添加如下宏
Main.c中添加如下静态变量
#ifdef BLE_DFU_APP_SUPPORT
static ble_dfu_t m_dfus;
#endif
Main
.c中添加如下3个函数
- #ifdef BLE_DFU_APP_SUPPORT
- /**@brief Function for stopping advertising.
- */
- static void advertising_stop(void)
- {
- uint32_t err_code;
- err_code = sd_ble_gap_adv_stop();
- APP_ERROR_CHECK(err_code);
- err_code = bsp_indication_set(BSP_INDICATE_IDLE);
- APP_ERROR_CHECK(err_code);
- }
- /**@brief Function for loading application-specific context after establishing a secure connection.
- *
- * @details This function will load the application context and check if the ATT table is marked as
- * changed. If the ATT table is marked as changed, a Service Changed Indication
- * is sent to the peer if the Service Changed CCCD is set to indicate.
- *
- * @param[in] p_handle The Device Manager handle that identifies the connection for which the context
- * should be loaded.
- */
- static void app_context_load(dm_handle_t const * p_handle)
- {
- uint32_t err_code;
- static uint32_t context_data;
- dm_application_context_t context;
- context.len = sizeof(context_data);
- context.p_data = (uint8_t *)&context_data;
- err_code = dm_application_context_get(p_handle, &context);
- if (err_code == NRF_SUCCESS)
- {
- // Send Service Changed Indication if ATT table has changed.
- if ((context_data & (DFU_APP_ATT_TABLE_CHANGED << DFU_APP_ATT_TABLE_POS)) != 0)
- {
- err_code = sd_ble_gatts_service_changed(m_conn_handle, APP_SERVICE_HANDLE_START, BLE_HANDLE_MAX);
- if ((err_code != NRF_SUCCESS) &&
- (err_code != BLE_ERROR_INVALID_CONN_HANDLE) &&
- (err_code != NRF_ERROR_INVALID_STATE) &&
- (err_code != BLE_ERROR_NO_TX_BUFFERS) &&
- (err_code != NRF_ERROR_BUSY) &&
- (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING))
- {
- APP_ERROR_HANDLER(err_code);
- }
- }
- err_code = dm_application_context_delete(p_handle);
- APP_ERROR_CHECK(err_code);
- }
- else if (err_code == DM_NO_APP_CONTEXT)
- {
- // No context available. Ignore.
- }
- else
- {
- APP_ERROR_HANDLER(err_code);
- }
- }
- /** @snippet [DFU BLE Reset prepare] */
- /**@brief Function for preparing for system reset.
- *
- * @details This function implements @ref dfu_app_reset_prepare_t. It will be called by
- * @ref dfu_app_handler.c before entering the bootloader/DFU.
- * This allows the current running application to shut down gracefully.
- */
- static void reset_prepare(void)
- {
- uint32_t err_code;
- if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
- {
- // Disconnect from peer.
- err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
- APP_ERROR_CHECK(err_code);
- err_code = bsp_indication_set(BSP_INDICATE_IDLE);
- APP_ERROR_CHECK(err_code);
- }
- else
- {
- // If not connected, the device will be advertising. Hence stop the advertising.
- advertising_stop();
- }
- err_code = ble_conn_params_stop();
- APP_ERROR_CHECK(err_code);
- nrf_delay_ms(500);
- }
- /** @snippet [DFU BLE Reset prepare] */
- #endif // BLE_DFU_APP_SUPPORT
以上代码都用 BLE_DFU_APP_SUPPORT 这个宏来包含了,所以添加这些代码后需要在工程设置中添加一下这个宏的定义。
PS:官方的 hrs例子中 存在带dfu的工程,打开那个工程直接全局搜这个宏就能找到上面这个和 dfu相关的代码了。这里的代码就是从那里拷贝过来的。
Main.c中的services_init函数的最后添加 创建dfu 服务的代码
static void services_init(void)
{
………………………
……………………….
#ifdef BLE_DFU_APP_SUPPORT
/** @snippet [DFU BLE Service initialization] */
ble_dfu_init_t dfus_init;
// Initialize the Device Firmware Update Service.
memset(&dfus_init, 0, sizeof(dfus_init));
dfus_init.evt_handler = dfu_app_on_dfu_evt;
dfus_init.error_handler = NULL;
dfus_init.evt_handler = dfu_app_on_dfu_evt;
dfus_init.revision = DFU_REVISION;
err_code = ble_dfu_init(&m_dfus, &dfus_init);
APP_ERROR_CHECK(err_code);
dfu_app_reset_prepare_set(reset_prepare);
dfu_app_dm_appl_instance_set(m_app_handle);
#endif // BLE_DFU_APP_SUPPORT
}
事件派发函数中 添加 dfu的事件处理函数以及 device_manage模块的事件处理函数
Main.c中再添加如下函数和代码
这时候编译下载程序后使用nordic 官方软件 nrf master control panel软件 连接设备后可以看到右上角有一个DFU的小图标。点击那个就可以升级了。
PS:别忘了需要烧写bootloader
那么点击这个dfu图标后是如何跳转bootloader进入升级模式的呢。
前面我们在service_init 中添加了dfu服务的创建。
函数中设置了一个回调函数 dfu_app_on_dfu_evt
当点击 DFU 图标后就会发送一条启动指令,dfu_app_on_dfu_evt函数中会处理这条指令
Bootloader_start函数就是做一些设置然后跳转到bootloader中去
上面的 sd_power_gpregret_set函数就是设置一个标记,表明从是从app 中启动进入bootloader的。
和前面说的bootloader代码中开头的判断所对应
Sdk中的bootloader例子是基于32Kram的51422,如果使用16kram的51822需要修改如下地址
另外,一旦烧录bootloader后设备上电启动顺序不再是 协议栈->app
而是 协议栈->bootloader->app
在bootloader中会对app是否有效做判断,sdk中的实现是只有通过DFU模式升级的app才会被认为有效的,应为DFU模式升级后会设置一些相关标志。 DFU判断app是不是有效就是判断这些标志, 所以如果你用烧录软件烧写app,因为没有设置一些标志,所以bootloader会认为 app无效,从而不会启动app。
不过可以通过修改一下变量来实现,即使通过烧录软件烧录app,bootloader也可以正常启动app。
打开bootlaoder工程中的bootloader_settings.c文件,修改如下变量就可以了。
如果应用程序中使用了看门狗那么dfu中也需要喂狗,51822的看门狗打开后就会一直运行。
看门狗最好设置成 睡眠时看门狗也停止,这样方便处理,只要在 唤醒后喂狗就行了,睡眠多久无所谓,因为睡眠时看门狗不会计数
直接将下面两个值设置为0。
bootloader代码中添加喂狗代码
PS:如果使用的SDK是高版本,并且程序中本身已经 有 自定义基准UUID的话,这个时候添加DFU代码需要修改 基准UUID允许添加的个数,因为默认是1,本身服务已经添加了, 而DFU服务也是使用自定义 基准UUID的,这个时候
ble_dfu_init
初始化函数里面调用
sd_ble_uuid_vs_add 函数时就会出错了。所以修改允许设置的自定义UUID个数。 如下修改: