单片机学习小组
直播中

vinww特烦恼

8年用户 1112经验值
擅长:存储技术
私信 关注

请问UART和自定义协议如何进行设备的固件升级?

UART和自定义协议如何进行设备的固件升级?

回帖(1)

刘晓燕

2022-2-14 10:38:07
        NRF52832的程序升级,即DFU,有通过无线方式(OTA)升级,也有通过UART,USB等硬件接口进行升级,目前资料最多的是通过无线方式进行升级,大家可以参考“青风带你学蓝牙”、“[艾克姆科技]nRF52832开发指南”等系列资料。
        本篇文章主要记录利用UART和自定义协议如何进行设备的固件升级。对于UART,相信大家都很清楚,那么什么叫自定义协议升级呢?就是指传输新固件的时候用自己定义的协议,不用管NRF官方的的传输协议。
        本文记录的方法是在支持OTA升级(Dual Bank Flash)的基础上修改的,使用此方法前可先了解OTA方式升级,所以使用此方法即可保证OTA不受影响,又能利用有线方式进行升级。此方法还有一个用处就是,如果用UART搭载4G模块,便可实现远程升级。
     
1.得到新固件

        使用OTA升级时,我们会用boot.hex、SDK.hex、APP.hex以及一些命令去生成一个压缩包文件,然后用nRF Connect手机App升级时,选择用此压缩包进行升级。当我们用解压工具解压此压缩包是会得到三个文件:app.bin;app.dat;manifest.json。没错,app.bin就是我们需要传输的新固件。

2.传输新固件(远程传输)
        我们得到新固件app.bin后,可以利用串口去传输它到NRF52832中,为防止传输过程中出现数据丢包和数据错误的情况,最好自己定义一个协议,将新固件打包传输,在NRF52832中按照协议解析,当解析出错时,进行固件重传。


        如何进行远程传输?可用4G模块或者其他可远程传输数据的模块,连接到NRF52832上,例如使用4G模块EC20,将EC20用串口UART和NRF52832相连,EC20连接到你自己的远程服务器上,在服务器上开一个TCPserver,服务器上使用TCP发送固件,EC20接收到后通过串口将固件透传给NRF52832。


        我们可以在应用程序中或者boot loader中添加传输新固件的代码。


3.存储新固件
        NRF52832使用OTA升级时,NRF52832应用程序存储区分为bank0、bank1两个区,bank0存储的是当前正在运行的固件,bank1存储的是新固件。通过调试发现bank0的起始地址是固定的0x26000,bank1的起始地址是=bank0的起始地址+bank0的app大小并页对齐,boot loader中也提供了相应的计算函数:


uint32_t nrf_dfu_bank1_start_addr(void)
{
    uint32_t bank0_addr = nrf_dfu_bank0_start_addr;
    return ALIGN_TO_PAGE(bank0_addr + s_dfu_settings.bank_0.image_size);
}
4.使新固件骗过BootLoader,让BootLoader认为新固件有效
        我们将新固件已经存到了bank1区域,但是bank0才是当前程序运行区域,也就是还需要将程序copy到bank0,在OTA的bootloader中,已经有这部分代码了,只是要让BootLoader检测到bank1的新固件合法有效才会去执行这步操作。


        怎么让BootLoader认为我们bank1的新固件合法有效呢?其中最重要的就是s_dfu_settings这个结构体,它的值是在BootLoader启动时,从flash中获取并通过校验的,对应BootLoader中的函数是


void nrf_dfu_settings_reinit(void)
        所以,我们要使bank1中的固件能通过BootLoader的校验,就需要更改s_dfu_settings的值,它是一个结构体变量,包含了bank0、bank1存储的固件大小,CRC校验,以及自身数据的校验值等等,当BootLoader检测到s_dfu_settings的检验数据不正确时,还有一个备份s_dfu_settings数据的flash区,会从备份区域给s_dfu_settings赋值并更新原s_dfu_settings存储区的值。


        接下来就是如何更改s_dfu_settings?更改s_dfu_settings数据,可以在BootLoader从flash读取到数据以后再更改,也可以在读取之前去将要更改的数据存到相应flash中(如果此段flash可以用app读写,则用此方法实现升级可以不用修改BootLoader代码,完全由APP实现)。


        为区分OTA和UART升级,在UART传输完成后,需要设置一个标志位,标志位符合UART升级事件再执行修改s_dfu_settings的操作。修改数据在“void nrf_dfu_settings_reinit(void)”函数中完成。


        修改s_dfu_settings数据:


        1>s_dfu_settings.progressv.update_start_address  新固件的起始存储地址,对于我而言固定为0x3E000,也可以利用上面的函数去计算;


        2>s_dfu_settings.bank_1.image_size 新固件大小;


        3>s_dfu_settings.bank_1.image_crc 新固件CRC检验值,此值可用BootLoader提供的现有函数"crc32_compute()"计算而得;


        4>s_dfu_settings.bank_1.bank_code 更改为“NRF_DFU_BANK_VALID_APP”,表示有效的APP;


        5>s_dfu_settings.crc “s_dfu_settings“的校验值,因为我们已经把s_dfu_settings中的数据改了,所以CRC就和之前不同,应该重新计算后赋值,利用BootLoader现有的函数”settings_crc_get“去计算此值;


        6>s_dfu_settings.boot_validation_app.bytes  将s_dfu_settings.bank_1.image_crc复制给它;


        7>修改完成后,还需要把这些数据更新到相应flash中,以便下次重启后通过检验,用到的函数是“”nrf_dfu_settings_write";


        8>修改备份s_dfu_settings区域数据有效性判断条件,如果有效,则会把备份区的数据重新赋值给s_dfu_settings,以上工作就做了无用功,所以判断用于区分OTA和UART升级的标志位,在UART升级时,不要用备份区数据去覆盖已经修改过的s_dfu_settings数据;


        9>清除用于区分OTA和UART升级的标志位。


我修改后的代码展示


void nrf_dfu_settings_reinit(void)
{
    bool settings_valid        = settings_crc_ok();//校验flash中的s_dfu_settings数据
    bool settings_backup_valid = settings_backup_crc_ok();//校验flash中备份的s_dfu_settings数据

    if (settings_valid)//如果flash中的s_dfu_settings数据校验有效
    {
        NRF_LOG_DEBUG("Using settings page.");
        memcpy(&s_dfu_settings, m_dfu_settings_buffer, sizeof(nrf_dfu_settings_t));//将flash中的数据复制给s_dfu_settings

        /**************UART升级更改s_dfu_settings数据***************/
        uint32_t new_firmware_init=0x69000;//app中新固件传输完成,我将标志位和新固件大小写到此flash
        uint8_t  new_firmware_flag[8]={0};
        nrf_dfu_flash_read(new_firmware_init, new_firmware_flag, 8);//读取标志位,次函数自己根据原有的读flash修改的,BootLoader中没有,自己稍微修改一下或者用原来的,功能没有区别,只是为了更方便使用
        if(new_firmware_flag[0]==0x01)
        {
            s_dfu_settings.progress.update_start_address = 0x3E000;//bank1起始位置
            s_dfu_settings.bank1.image_size = ;/*新固件大小*/
            s_dfu_settings.bank1.image_crc  = crc32_compute((uint8_t*)(s_dfu_settings.progress.update_start_address),s_dfu_settings.bank1.image_size,NULL);//计算新固件crc
            s_dfu_settings.bank1.bank_code  = NRF_DFU_BANK_VALID_APP;//新固件有效
            s_dfu_settings.crc = settings_crc_get(&s_dfu_settings);//重新计算s_dfu_settings的crc
            memcpy(s_dfu_settings.boot_validation_app.bytes, &s_dfu_settings.bank1.image_crc, 4);//也是存储新固件crc的位置
            ret_code_t err_code = nrf_dfu_settings_write(NULL);//将s_dfu_settings的值更新到flash中
            if(err_code != NRF_SUCCESS) return;
            nrf_dfu_flash_erase(new_firmware_init, 1, NULL);//清除UART升级标志位
        }
        else if (settings_backup_valid)//如果flash中备份的数据有效
        /***********************************************************/

        {
            NRF_LOG_DEBUG("Copying forbidden parts from backup page.");
            settings_forbidden_parts_copy_from_backup((uint8_t *)&s_dfu_settings);
        }
    }

    /*以下程序未修改,此处展示时省略*/
}


        到此,升级需要修改的代码就完成了。更改s_dfu_settings数据后,BootLoader检测带新固件有效就会去从bank1 copy新固件到bank0,并自动更新其他相关的数据和写flash,比如备份s_dfu_settings数据的flash会自动被跟新,不需要我们去更改。然后BootLoader跳转到app中,此时的app就是升级后的app。


        本文重点是第4步,理解之后就能运用了。如果能用蓝牙去广播新固件,用此方法是不是可以实现批量升级?如果可以用蓝牙广播固件,丢包、传输速度又是问题,如何提高升级的效率?
举报

更多回帖

发帖
×
20
完善资料,
赚取积分