CN / EN
文档反馈
感谢关注汇顶文档,期待您的宝贵建议!
感谢您的反馈,祝您愉快!
文档中心 > GR5xx固件升级开发指南/ DFU移植方式详解 Copy URL

DFU移植方式详解

为方便用户将GR5xx提供的DFU方案应用到自定义的工程中,本章以ble_app_template_freertos示例工程为例(工程位于SDK_Folder\projects\ble\ble_peripheral\ble_app_template_freertos\Keil_5),介绍为该工程移植DFU功能的操作。移植过程主要包括以下步骤。

  1. DFU初始化
  2. 添加DFU调度器
  3. DFU服务初始化(使用低功耗蓝牙通讯方式)及数据接收处理
  4. 添加DFU组件
  5. 添加OTA Profile

具体操作步骤如下:

  1. 初始化DFU,代码位于user_periph_setup.c文件的app_periph_init函数中,如下所示:
    #include “dfu_port.h”
    void app_periph_init(void)
    {
        dfu_uart_init();
        dfu_uart_ctrl_pin_init();
        dfu_port_init(uart_send_data, DFU_FW_SAVE_ADDR, &dfu_pro_call);
    }
    

    dfu_uart_init()是串口初始化函数,当使用串口通讯方式时,需要初始化串口,以GR551x为例,使用UART1进行数据传输,初始化代码如下所示:

    static app_uart_params_t dfu_uart_param;
    #define DFU_UART_RX_BUFF_SIZE  0x400
    #define DFU_UART_TX_BUFF_SIZE  0x400
    static uint8_t s_dfu_uart_rx_buffer[DFU_UART_RX_BUFF_SIZE];
    static uint8_t s_dfu_uart_tx_buffer[DFU_UART_TX_BUFF_SIZE];
    
    static void dfu_uart_init(void)
    {
        app_uart_tx_buf_t uart_buffer;
    
        uart_buffer.tx_buf       = s_dfu_uart_tx_buffer;
        uart_buffer.tx_buf_size  = DFU_UART_TX_BUFF_SIZE;
    
        dfu_uart_param.id                   = APP_UART1_ID;
        dfu_uart_param.init.baud_rate       = APP_UART_BAUDRATE;
        dfu_uart_param.init.data_bits       = UART_DATABITS_8;
        dfu_uart_param.init.stop_bits       = UART_STOPBITS_1;
        dfu_uart_param.init.parity          = UART_PARITY_NONE;
        dfu_uart_param.init.hw_flow_ctrl    = UART_HWCONTROL_NONE;
        dfu_uart_param.init.rx_timeout_mode = UART_RECEIVER_TIMEOUT_ENABLE;
        dfu_uart_param.pin_cfg.rx.type      = APP_UART1_RX_IO_TYPE;
        dfu_uart_param.pin_cfg.rx.pin       = APP_UART1_RX_PIN;
        dfu_uart_param.pin_cfg.rx.mux       = APP_UART1_RX_PINMUX;
        dfu_uart_param.pin_cfg.rx.pull      = APP_UART_RX_PULL;
        dfu_uart_param.pin_cfg.tx.type      = APP_UART1_TX_IO_TYPE;
        dfu_uart_param.pin_cfg.tx.pin       = APP_UART1_TX_PIN;
        dfu_uart_param.pin_cfg.tx.mux       = APP_UART1_TX_PINMUX;
        dfu_uart_param.pin_cfg.tx.pull      = APP_UART_TX_PULL;
    
        app_uart_init(&dfu_uart_param, dfu_uart_evt_handler, &uart_buffer);
        app_uart_receive_async(APP_UART1_ID, s_dfu_uart_rx_buffer, 
                               sizeof(s_dfu_uart_rx_buffer));
    }
    

    在使用UART升级时,除了初始化串口的TX和RX引脚以外,还需初始化一个控制引脚,该控制引脚的作用:唤醒正在睡眠的设备;启动已停止的DFU任务。因此,如果用户的设备采用了睡眠机制,必须选择AON类型的引脚作为控制引脚。以GR551x为例,选择AON_GPIO_PIN_1作为控制引脚,初始化代码如下所示:

    #define DFU_UART_CTRL_PIN    AON_GPIO_PIN_1
    
    void dfu_uart_ctrl_pin_init(void)
    {
        app_io_init_t io_init = APP_IO_DEFAULT_CONFIG;
    
        io_init.pull = APP_IO_PULLUP;
        io_init.mode = APP_IO_MODE_IT_FALLING;
        io_init.pin  = DFU_UART_CTRL_PIN;
        io_init.mux  = APP_IO_MUX;
        app_io_init(APP_IO_TYPE_AON, &io_init);
    
        app_io_event_register_cb(APP_IO_TYPE_AON, &io_init, app_io_event_handler, 
                                 "DFU uart ctrl pin interrupt");
    }
    

    在应用固件中,DFU_FW_SAVE_ADDR表示当进行后台双区升级时,固件拷贝的起始地址,其值设置为:

    #define DFU_FW_SAVE_ADDR (FLASH_START_ADDR + 0x60000)
    说明:

    该值不是固定的,设置时请注意不能和App bootloader固件以及Bank0固件的地址冲突。

    uart_send_data是串口发送数据函数,使用串口通讯方式升级时,需要注册该接口,该函数定义如下所示:

    static void uart_send_data(uint8_t *data, uint16_t size)
    {
        app_uart_transmit_async(APP_UART1_ID, data, size);
    }
    
    

    dfu_pro_call是升级过程中升级进度打印的回调函数,详细代码如下所示:

    static void dfu_program_start_callback(void);
    static void dfu_programing_callback(uint8_t pro);
    static void dfu_program_end_callback(uint8_t status);
    
    static dfu_pro_callback_t dfu_pro_call =
    {
        .dfu_program_start_callback = dfu_program_start_callback,
        .dfu_programing_callback = dfu_programing_callback,
        .dfu_program_end_callback = dfu_program_end_callback,
    };
    
    static void dfu_program_start_callback(void)
    {
        APP_LOG_DEBUG("Dfu start program");
    }
    
    static void dfu_programing_callback(uint8_t pro)
    {
        APP_LOG_DEBUG("Dfu programing---%d%%", pro);
    }
    
    static void dfu_program_end_callback(uint8_t status)
    {
        APP_LOG_DEBUG("Dfu program end");
        if (0x01 == status)
        {
            APP_LOG_DEBUG("status: successful");
        }
        else
        {
            APP_LOG_DEBUG("status: error");
        }
    }
    
  2. 创建DFU信号量及DFU调度任务。
    #include “dfu_port.h”
    
    #define DFU_TASK_STACK_SIZE             ( 1024 * 2 )
    TaskHandle_t         dfu_task_handle;
    SemaphoreHandle_t    xDfuSemaphore;
    uint8_t              start_dfu_task_flag = 0;
    
    
    int main(void)
    {
        ……
        xDfuSemaphore = xSemaphoreCreateBinary();
        xTaskCreate(vStartTasks, "create_task", 512, NULL, 0, NULL);
        vTaskStartScheduler();
        for(;;);
    }
    
    static void vStartTask(void *arg)
    {
        ……
        XTaskCreate(dfu_schedule_task, "dfu_schedule_task", DFU_TASK_STACK_SIZE, NULL, 
                    configMAX_PRIORITIES - 2,  &dfu_task_handle);
        ……
        vTaskDelete(NULL);
    }
    
    
    
    static void dfu_schedule_task(void *p_arg)
    {
        while (1)
        {
            if (!start_dfu_task_flag)
            {
                xSemaphoreTake(xDfuSemaphore, portMAX_DELAY);
            }
            dfu_schedule();    
        }
    }
    

    实际应用中,在主机端与设备端不进行数据交互时,需要暂停DFU任务的运行,以降低功耗。GR5xx提供的方案是为DFU任务添加超时机制及信号量。如上述代码所示,DFU任务创建后,即在等待信号量,获取到信号量后,开始运行DFU任务。DFU信号量在不同通讯方式下释放方式不同,低功耗蓝牙通过Control Point特性下发DFU Enter命令释放信号量;UART通过控制引脚触发外部中断的形式释放信号量。

    当设备端超过一定时间没有接收到主机下发的数据时,则判定为当前DFU任务已结束,在超时回调函数里将DFU任务开始标志位复位,停止DFU任务的运行,继续等待DFU信号量。超时机制通过app_timer软件定时器组件实现。在使用app_timer时需要包含#include “app_timer.h”,定时器初始化函数定义如下,该函数需要在app_periph_init()函数内进行调用。

    static app_timer_id_t  s_cmd_wait_timer_id;
    
    static void dfu_timer_init(void)
    {
        app_timer_create(&s_cmd_wait_timer_id, ATIMER_REPEAT, cmd_wait_timeout_handler);
    }
    

    超时回调函数的定义如下所示:

    static uint16_t        s_dfu_last_count;
    static uint16_t        s_dfu_curr_count;
    extern uint8_t         start_dfu_task_flag;
    
    static void cmd_wait_timeout_handler(void* p_arg)
    {
        if (s_dfu_last_count < s_dfu_curr_count)
        {
            s_dfu_last_count = s_dfu_curr_count;
        }
        else
        {
            s_dfu_curr_count = 0;
            s_dfu_last_count = 0;
            dfu_cmd_parse_state_reset();
            fast_dfu_state_machine_reset();
            start_dfu_task_flag = 0;
            dfu_timer_stop();
        }
    }
    

    由上述代码可知,当主机端超过一定时间没有发送数据至设备端后,会触发超时函数,超时函数会调用dfu_cmd_parse_state_reset函数,该函数由SDK内部实现,可直接调用,具体作用是复位DFU命令解析的状态机,以免影响下次升级时数据解析。fast_dfu_state_machine_reset函数也需要在超时函数里进行调用,作用是复位快速模式的状态机,以免影响下次升级时主机端与设备端的数据传输。GR551x SDK V2.0.1和GR5525 SDK V0.8.0的dfu_port.c里目前没有实现fast_dfu_state_machine_reset接口,需要自行实现,实现代码如下所示:

    void fast_dfu_state_machine_reset(void)
    {
        s_fast_dfu_mode = 0;
        s_program_end_flag = 0;
        s_fast_dfu_state = FAST_DFU_INIT_STATE;
    }
    

    然后将DFU任务开始标志位复位,最后停止DFU软件定时器。软件定时器停止函数定义如下:

    static void dfu_timer_stop(void)
    {
        app_timer_stop(s_cmd_wait_timer_id);
    }
    
  3. 服务初始化及数据接收处理。当使用低功耗蓝牙通讯方式进行DFU时,在user_app.c文件的service_init函数中初始化DFU相关服务,并添加头文件#include “dfu_port.h” ,如下所示:
    static void services_init(void)
    {
        dfu_service_init(dfu_enter);
    }
    

    dfu_enter函数定义默认为空,用户可在函数内部添加DFU任务触发代码,另一方面,为了实现DFU的超时机制,在dfu_enter函数里调用定时器开始函数。当设备端收到DFU Enter指令后,释放DFU信号量、将DFU任务开始标志位置位,开启DFU超时机制的软件定时器。添加的代码如下所示:

    extern SemaphoreHandle_t    xDfuSemaphore;
    extern uint8_t         start_dfu_task_flag;
    
    static void dfu_enter(void)
    {
        if (!start_dfu_task_flag)
        {
            start_dfu_task_flag = 1;
            xSemaphoreGive(xDfuSemaphore);
            dfu_timer_start();
        }
    }
    

    dfu_timer_start的代码实现如下所示:

    #define DFU_CMD_WAIT_TIMEOUT            4000
    
    void dfu_timer_start(void)
    {
        app_timer_start(s_cmd_wait_timer_id, DFU_CMD_WAIT_TIMEOUT, NULL);
    }
    

    如果是以UART方式进行通讯,则没有dfu_enter命令,也没有对应的dfu_enter回调函数。UART通讯方式的DFU任务唤醒方法使用在步骤1初始化的控制引脚的中断服务函数里完成,中断服务函数定义如下:

    extern SemaphoreHandle_t    xDfuSemaphore;
    extern uint8_t              start_dfu_task_flag;
    
    void app_io_event_handler(app_io_evt_t *p_evt)
    {
        app_io_pin_state_t pin_level = APP_IO_PIN_RESET;
    
        if (p_evt->pin == DFU_UART_CTRL_PIN)
        {
            pin_level = app_io_read_pin(APP_IO_TYPE_AON, DFU_UART_CTRL_PIN); 
            if (pin_level == APP_IO_PIN_RESET)
            {
                do
                {
                    pin_level = app_io_read_pin(APP_IO_TYPE_AON, DFU_UART_CTRL_PIN);
                } while(pin_level == APP_IO_PIN_SET);
                
                if (!start_dfu_task_flag)
                {
                    start_dfu_task_flag = 1; 
                    xSemaphoreGive(xDfuSemaphore); 
                    dfu_timer_start();
                }
            }
        }
    }
    

    开启DFU软件定时器后,需要在数据接收位置调用count累加的函数,count累加函数定义如下所示:

    void dfu_rev_cmd_count(void)
    {
        s_dfu_curr_count++;
    }
    
    

    当以低功耗蓝牙通讯方式进行DFU时,在低功耗蓝牙接收数据处调用dfu_rev_cmd_count函数,具体位置为dfu_port.c文件的otas_evt_process(otas_evt_t *p_evt)函数内,dfu的数据接收函数也在此添加,具体代码如下所示:

    static void otas_evt_process(otas_evt_t *p_evt)
    {
        switch(p_evt->evt_type)
        {
            ......
            case OTAS_EVT_RX_RECEIVE_DATA:
            dfu_rev_cmd_count();
            if (!s_fast_dfu_mode || s_program_end_flag)
            {
                dfu_ble_receive_data_process(p_evt->p_data, p_evt->length);
            }
            else
            {
                s_fast_dfu_state = FAST_DFU_PROGRAM_FLASH_STATE; 
                fast_dfu_write_data_to_buffer(p_evt->p_data, p_evt->length);
            }
            break;
            ......
        }
    }
    

    当以UART方式进行DFU时,在UART接收数据处调用dfu_rev_cmd_count函数,即在dfu_uart_evt_handler函数内进行调用,dfu的数据接收函数也在此进行添加,具体代码如下所示:

    void dfu_uart_evt_handler(app_uart_evt_t * p_evt)
    {
        switch(p_evt->evt_type)
        {
        ......
            case APP_UART_EVT_RX_DATA: 
            dfu_rev_cmd_count();
            dfu_uart_receive_data_process(s_dfu_uart_rx_buffer, p_evt->data.size); 
            app_uart_receive_async(APP_UART1_ID, s_dfu_uart_rx_buffer, 
                                   DFU_UART_RX_BUFF_SIZE);
            break;
        ......
        }
    }
    
    说明:

    在手机端当前连接设备的DFU任务尚处于停止状态时,点击升级按钮前,若需通过 GRToolbox 获取固件信息,可通过写控制点的方式手动下发唤醒DFU任务的命令,具体操作请参考固件升级

  4. 在gr_libraries和gr_profiles工程文件夹下分别添加dfu_port.cotas.c文件,如下图所示。
    说明:

    dfu_port.cotas.c文件分别位于SDK_Folder\components\libraries\dfu_portSDK_Folder\components\profiles\otas

    图 47 Keil工程列表
  5. custom.config.h中设置APP_CODE_LOAD_ADDR和APP_CODE_RUN_ADDR为FLASH_START_ADDR + 0x30000,使其和boot固件不会产生覆盖的情况。因此,以GR551x为例,APP_CODE_RUN_ADDR和APP_CODE_LOAD_ADDR设置为0x01030000。
    // <o> Code load address
    // <i> Default:  0x01002000
    #define APP_CODE_LOAD_ADDR      0x01030000
    
    // <o> Code run address
    // <i> Default:  0x01002000
    #define APP_CODE_RUN_ADDR       0x01030000
    
  6. 为使App bootloader固件能正确跳转至应用固件,需匹配两者的COMMENTS。App bootloader固件默认COMMENTS为"ble_app_temp",因此在custom.config.h中设置ble_app_template_freertos固件的COMMENTS如下:
    #define APP_FW_COMMENTS                        "ble_app_temp"

扫描关注

打开微信,使用“扫一扫”即可关注。