NRF52832-DFU-Bootloader源码详解
Bootloader源码:nRF5_SDK_15.0.0_a53641aexamplesdfusecure_bootloaderpca10040_ble
Keil5中的C/C++中宏定义为:
APP_TIMER_V2 APP_TIMER_V2_RTC1_ENABLED BLE_STACK_SUPPORT_REQD BOARD_PCA10040 CONFIG_GPIO_AS_PINRESET FLOAT_ABI_HARD NRF52 NRF52832_XXAA NRF52_PAN_74 NRF_DFU_SETTINGS_VERSION=1 NRF_DFU_SVCI_ENABLED NRF_SD_BLE_API_VERSION=6 S132 SOFTDEVICE_PRESENT SVC_INTERFACE_CALL_AS_NORMAL_FUNCTION __HEAP_SIZE=0 uECC_ENABLE_VLI_API=0 uECC_OPTIMIZATION_LEVEL=3 uECC_SQUARE_FUNC=0 uECC_SUPPORT_COMPRESSED_POINT=0 uECC_VLI_NATIVE_LITTLE_ENDIAN=1
- main.c
ret_val = nrf_bootloader_flash_protect(0, MBR_SIZE, false);
这是将MBR区域(前0X1000)进行写保护, MBR是在FLASH中最靠前的一段程序。
ret_val = nrf_bootloader_flash_protect(BOOTLOADER_START_ADDR, BOOTLOADER_SIZE, false);这是将Bootloader程序区域进行写保护。
BOOTLOADER_START_ADDR由keil中的配置决定。BOOTLOADER_START_ADDR=0X70000
BOOTLOADER_SIZE = NRF_MBR_PARAMS_PAGE_ADDRESS-BOOTLOADER_START_ADDR
= 0X7E000 – 0X70000 = 0XE000
IRAM1的起始地址不能设置太小,得留够协议栈的RAM。
NRF_LOG_DEFAULT_BACKENDS_INIT();初始化RTT调试输出,此处是关闭的,空操作。
static void dfu_observer(nrf_dfu_evt_type_t evt_type)//这是DFU过程各种事件的回调函数。
nrf_bootloader_app_start();//如果不能进入升级处理流程,将会进入此函数,跳转到用户APP程序运行。
ret_val = nrf_bootloader_init(dfu_observer);//DFU升级最关键的函数就是这,dfu_observer是回调函数。
1、ret_val = nrf_dfu_settings_init(false);设置参数的初始化,进入此函数内部看看。
ret_code_t rc = nrf_dfu_flash_init(sd_irq_initialized);//此处是操作FLASH前的初始化。
NRF52832的FLASH操作有两种方式,一种是nrf_fstorage_sd,非阻塞方式, 一种是nrf_fstorage_nvmc,阻塞方式。
一般无协议栈时用nvmc方式,而有协议栈时用sd方式。如果已经初始化协议栈,仍使能nvmc方式,则程序可能会出现异常,具体原因还有待查找。此处因DFU程序还未初始化协议栈,所以使用的是nvmc方式。p_api_impl = &nrf_fstorage_nvmc;
memcpy((void*)&s_dfu_settings, m_dfu_settings_buffer, sizeof(nrf_dfu_settings_t));
将FLASH中的设置参数复制到RAM中然后进行CRC校验,如果正确则设置参数有效,不需要初始化,否则进行设置参数的初始化。
memset(&s_dfu_settings, 0x00, sizeof(nrf_dfu_settings_t));
s_dfu_settings.settings_version = NRF_DFU_SETTINGS_VERSION;只是设置了一个设置参数的版本 。
rc = nrf_dfu_settings_write(NULL); 此函数会将FLASH中的设置参数与刚才初始化的参数进行比对,如果一样则跳过, 否则先按页(4K)擦除,然后更新s_dfu_settings的CRC值,最后写入FALSH中。
- activation_result = nrf_bootloader_fw_activate();//检查bank1是否有固件待更新
先了解下NRF52832的FLASH布局情况。DFU升级使用的是双区升级。
NRF52832的FLASH布局如上图,MBR就是最先运行的那部分程序,是厂家固化FLASH中,无法擦除的,大小固定为0X1000。softdevice紧跟其后,大小与协议栈的版本有关,s132_nrf52_6.0.0_softdevice就占用152K,application紧跟协议栈,超始地址为0X26000。
对比下协议栈s132_nrf52_6.0.0与s132_nrf52_5.0.0可以看出,MBR好像又是包含在协议栈中的。
Dfu bootloader的起始地址也不一定是0X78000,是可以调整的。而Dfu bootloader的起始地址前的3页(12K)是用户数据存储区。
Bootloader setting起始地址是0X7F000,大小0X1000,这是固定不变的。
MBR parameters 起始地址是0X7E000, 大小0X1000, 这也是固定不变的。
这样用户APP区大小为512-152-32-12=316K,再分为两个bank, bank0起始地址为:0X26000,保存的是最终的用户APP程序,bank1起始地址为bank0的结束地址,页对齐。Bank1在DFU升级时用来保存升级包数据,后续将会覆盖bank0。所以用户APP程序最大为158K。
进入到nrf_bootloader_fw_activate函数内部。
case NRF_DFU_BANK_VALID_APP: ret_val = app_activate();//bank1为有效的APP程序,则复制到bank0区域
case NRF_DFU_BANK_VALID_SD:ret_val = sd_activate();//bank1为有效的SD程序,则复制到协议栈起始地址0X1000处
case NRF_DFU_BANK_VALID_BL:ret_val = bl_activate();//bank1为有效的bootloader程序,则通过nrf_dfu_mbr_copy_bl函数来复制
case NRF_DFU_BANK_VALID_SD_BL:ret_val = sd_bl_activate();//bank1为有效的bootloader程序及SD程序,则先复制SD程序,后复制BOOT程序。
case NRF_DFU_BANK_INVALID://bank1无效
default:
return ACTIVATION_NONE; //则不处理,后续将跳转到用户APP程序。
nrf_dfu_bank_invalidate(p_bank);//清除bank1有效标志
ret_val = nrf_dfu_settings_write(flash_write_callback);//更新参数到FLASH中。下次重启将不会执行复制操作。
根据执行上面函数的返回值作相应处理:
case ACTIVATION_NONE:
initial_timeout = NRF_BL_DFU_INACTIVITY_TIMEOUT_MS;//DFU升级时间限定为120秒。
dfu_enter = dfu_enter_check();//检查是否进入DFU升级
case ACTIVATION_SUCCESS_EXPECT_ADDITIONAL_UPDATE:
initial_timeout = NRF_BL_DFU_CONTINUATION_TIMEOUT_MS;
dfu_enter = true;
//如果SD已更新,而bank0有效,则进入DFU升级,限时10秒。
case ACTIVATION_SUCCESS:
bootloader_reset();//前面从bank1的复制操作成功,软件复位。
case ACTIVATION_ERROR:
default:
return NRF_ERROR_INTERNAL;//出错将会产生软件复位
if (dfu_enter)//如果进入DFU升级流程
nrf_bootloader_wdt_init();//初始化看门狗
scheduler_init();//调度相关初始化
dfu_enter_flags_clear();//清除进入DFU的相关标志
//进入DFU的方式有:NRF_BL_DFU_ENTER_METHOD_GPREGRET根据电源备份寄存器的 //值, 按键NRF_BL_DFU_ENTER_METHOD_BUTTONLESS,复位键 //NRF_BL_DFU_ENTER_METHOD_PINRESET。
ret_val = nrf_dfu_init_user();//什么也没干。
nrf_bootloader_dfu_inactivity_timer_restart(initial_timeout, inactivity_timeout);//开启超时定时器。
ret_val = nrf_dfu_init(dfu_observer);//DFU的初始化,与蓝牙相关,进入函数看看。
ret_val = nrf_dfu_transports_init(dfu_observer);//初始化蓝牙传输,调用函数ble_dfu_transport_init.进入函数查看。
err_code = nrf_balloc_init(&m_buffer_pool);//初始化内存池
err_code = ble_stack_init();//初始化协议栈
if (nrf_dfu_settings_adv_name_is_valid())
err_code = nrf_dfu_settings_adv_name_copy(&m_adv_name);
//如果setting中有广播名称则复制过来。
err_code = gap_params_init();//蓝牙通用连接参数初始化,如果setting中有蓝牙广播名称就用,没有就用默认的。设置最小最大连接间隔,从机延迟,超时时间。
err_code = ble_dfu_init(&m_dfu);//初始化设备固件更新服务
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&service_uuid,
&(p_dfu->service_handle));
//增加基本服务0xFE59到协议栈,获取服务句柄service_handle。
err_code = sd_ble_uuid_vs_add(&base_uuid128, &p_dfu->uuid_type);
//加入一个服务UUID 128bit到协议栈,获取UUID类型到uuid_type
err_code = dfu_pkt_char_add(p_dfu);
//增加数据包特性到服务,可写,DFU的升级数据包及初始化包通过此特性传输。
err_code = dfu_ctrl_pt_add(p_dfu);
//增加控制特性到服务 可写,通知功能,DFU升级的控制,通过此特性传输,通知功能用作应答。
err_code = advertising_start();//蓝牙广播的初始化及启动广播
//nrf_dfu_transports_init
ret_val = nrf_dfu_req_handler_init(dfu_observer);
//此函数中先nrf_dfu_flash_init(true); FLASH操作用sd非阻塞方式, 然后初始化签名安全认证。如果已经接收了初始化数据包,则进行签名认证。
继续nrf_bootloader_init函数:
loop_forever(); // This function will never return.
//进入死循环,根据事件回调,循环喂看门狗
Else
ret_val = nrf_dfu_settings_additional_erase();//清除掉setting最后部分,广播名称,参数。
nrf_bootloader_app_start();//开始执行APP程序。
关于回调函数dfu_observer
//此函数为 nrf_bootloader.c中的回调
static void dfu_observer(nrf_dfu_evt_type_t evt_type)
{
switch (evt_type)
{
//创建初始化包传输即认为是DFU升级开始
case NRF_DFU_EVT_DFU_STARTED://在on_cmd_obj_create_request中
//on_data_obj_execute_request每次接收到数据都会重启超时定时器
case NRF_DFU_EVT_OBJECT_RECEIVED:
LCD_Display_Updata_Cur();
nrf_bootloader_dfu_inactivity_timer_restart(NRF_BL_DFU_INACTIVITY_TIMEOUT_MS, inactivity_timeout);
break;
case NRF_DFU_EVT_DFU_COMPLETED:
case NRF_DFU_EVT_DFU_ABORTED:
bootloader_reset();
break;
default:
break;
}
if (m_user_observer)
{
m_user_observer(evt_type);
}
}
//nrf_bootloader.c中调用, 因 nrf_dfu_init 在nrf_dfu.c中被重写
ret_val = nrf_dfu_init(dfu_observer);
//nrf_dfu.c 重写nrf_dfu_init
uint32_t nrf_dfu_init(nrf_dfu_observer_t observer)
{
uint32_t ret_val;
//nrf_bootloader.c中的dfu_observer被用于nrf_dfu.c中的m_user_observer
m_user_observer = observer;
NRF_LOG_INFO(“Entering DFU mode.”);
dfu_observer(NRF_DFU_EVT_DFU_INITIALIZED);
// Initializing transports
ret_val = nrf_dfu_transports_init(dfu_observer);
if (ret_val != NRF_SUCCESS)
{
NRF_LOG_ERROR(“Could not initalize DFU transport: 0x%08x”, ret_val);
return ret_val;
}
ret_val = nrf_dfu_req_handler_init(dfu_observer);
return ret_val;
}
//此函数为nrf_dfu.c中的回调 dfu_observer,在 nrf_dfu_req_handler.c中被设置
ret_code_t nrf_dfu_req_handler_init(nrf_dfu_observer_t observer);
//nrf_dfu.c中dfu_observer被nrf_dfu_req_handler.c中的on_dfu_complete调用。
nrf_dfu.c static void dfu_observer(nrf_dfu_evt_type_t event)
{
switch (event)
{
case NRF_DFU_EVT_DFU_COMPLETED://所有固件传输完成
{
#ifndef NRF_DFU_NO_TRANSPORT
UNUSED_RETURN_VALUE(nrf_dfu_transports_close(NULL));//关闭连接
#endif
break;
}
default:
break;
}
/* Call user’s observer if present. */
if (m_user_observer)
{
m_user_observer(event);
}
}
//所有固件传输完成执行
nrf_dfu_req_handler.c static void on_dfu_complete(nrf_fstorage_evt_t * p_evt)
{
UNUSED_PARAMETER(p_evt);
NRF_LOG_DEBUG(“All flash operations have completed. DFU completed.”);
m_observer(NRF_DFU_EVT_DFU_COMPLETED);
}
nrf_dfu_req_handler.c static void on_data_obj_execute_request_sched(void * p_evt, uint16_t event_length)
{
UNUSED_PARAMETER(event_length);
nrf_dfu_request_t * p_req = (nrf_dfu_request_t *)(p_evt);
/* Wait for all buffers to be written in flash. */
if (nrf_fstorage_is_busy(NULL))
{
ret_code_t ret = app_sched_event_put(p_req,
sizeof(nrf_dfu_request_t),
on_data_obj_execute_request_sched);
if (ret != NRF_SUCCESS)
{
NRF_LOG_ERROR(“Failed to schedule object execute: 0x%x.”, ret);
}
return;
}
nrf_dfu_response_t res =
{
.request = NRF_DFU_OP_OBJECT_EXECUTE,
};
nrf_dfu_flash_callback_t dfu_settings_callback;
/* 所有固件传输完成 */
if (s_dfu_settings.progress.firmware_image_offset == m_firmware_size_req)
{
NRF_LOG_DEBUG(“Postvalidation of firmware image.”);
res.result = nrf_dfu_validation_post_data_execute(m_firmware_start_addr, m_firmware_size_req);
res.result = ext_err_code_handle(res.result);
/* Callback to on_dfu_complete() after updating the settings. */
dfu_settings_callback = (nrf_dfu_flash_callback_t)(on_dfu_complete);//回调函数在保存后setting后执行
on_dfu_complete->nrf_dfu.c::dfu_observer(关闭连接)->nrf_bootloader::dfu_observer(重启)
}
else
{
res.result = NRF_DFU_RES_CODE_SUCCESS;
/* No callback required. */
dfu_settings_callback = NULL;
}
/* Provide response to transport */
p_req->callback.response(&res, p_req->p_context);
/* Store settings to flash if the whole image was received or if configured
* to save progress information in flash.
*/
if ((dfu_settings_callback != NULL) || NRF_DFU_SAVE_PROGRESS_IN_FLASH)
{
ret_code_t ret = nrf_dfu_settings_write(dfu_settings_callback);//升级完成需要恢复且保存setting
UNUSED_RETURN_VALUE(ret);
}
NRF_LOG_DEBUG(“Request handling complete. Result: 0x%x”, res.result);
}
蓝牙事件回调函数
Nrf_dfu_ble.c
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
case BLE_GAP_EVT_CONNECTED: //建立连接后的处理
m_conn_handle = p_gap->conn_handle;//保存连接句柄
err_code = sd_ble_gap_conn_param_update(m_conn_handle, &m_gap_conn_params);//更新连接参数
case BLE_GAP_EVT_DISCONNECTED: //断开连接,自动广播
m_conn_handle = BLE_CONN_HANDLE_INVALID;//连接句柄无效
//数据包接收完成后会置位此标志,停止传输,不需要广播。
if (!(m_flags & DFU_BLE_RESETTING_SOON))
err_code = advertising_start();
case BLE_GATTS_EVT_WRITE: //蓝牙特性值写操作
on_write(&m_dfu, p_ble_evt);
//只有是写数据包特性才处理
if (p_write_evt->handle != p_dfu->dfu_pkt_handles.value_handle)
//申请缓存池
uint8_t * p_balloc_buf = nrf_balloc_alloc(&m_buffer_pool);
//将蓝牙接收数据复制到用户内存空间。
memcpy(p_balloc_buf, p_write_evt->data, p_write_evt->len);
nrf_dfu_request_t request =
{
.request = NRF_DFU_OP_OBJECT_WRITE,//对象写操作,无应答
.p_context = p_dfu,
.callback =
{
.response = ble_dfu_req_handler_callback,//回调函数,用来应答
.write = on_flash_write,//写入FLASH
}
};
ret_code_t rc = nrf_dfu_req_handler_on_req(&request);//触发用户层的事件回调
调用nrf_dfu_req_handler_req_process,此函数后面详解。
nrf_balloc_free(&m_buffer_pool, p_balloc_buf);释放内存池
case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST://更新当前的MTU值到手机端
//交换MTU请求,手机端发来一个MTU值,收到后比对是否小于MTU最大值247,如果小于,则去掉头部3字节,剩下的按4字节对齐,否则MTU值不变,最后应答当前的MTU值。
err_code = sd_ble_gatts_ex
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!