1 固件升级
在一些项目交期比较急的情况下,可以先把基本功能做出来,加入固件升级的功能,后续即使发现重大BUG,也不用返厂更新固件,只需要把加密固件发给客户自行更新,也可以使用物联网的方式进行远程固件升级。
2 FLASH划分
2.1 内部FLASH
以STM32F103ZET6为例,内部FLASH的扇区大小是2KB
2.2 外部FLASH
整个FLASH作为FAT32使用,以W25Q128为例,有16MB的空间
3 生成加密文件
3.1 加密算法
用的是tinycrypt中的aes加密算法修改IV和KEY的定义
#define TEST_TINY_AES_IV
"0123456789ABCDEF"
#define TEST_TINY_AES_KEY
"0123456789ABCDEF0123456789ABCDEF"
在使用前选择是作为加密还是解密
tinycrypt_enc_init();
tinycrypt_dec_init();
3.2 压缩算法
用的是quicklz压缩算法,压缩率比较低,154KB的固件压缩之后是118KB
3.3 app再加工
在keil编译出app固件后,使用外部应用程序来对app进行压缩,再进行加密,再为整个固件增加CRC32校验,外部程序在linux平台下同样适用,只需要在目标主机上编译生成目标文件即可。
3.3.1 参数检查
主要有FLASH总大小、FLASH扇区大小、bootloader大小、bootloader输入文件名、app输入文件名、输出app+crc文件名、输出app加密压缩后的文件名等
if (argc == 13)
{
combine_file.size_total = atoi(argv[1]);
combine_file.size_sector = atoi(argv[2]);
combine_file.size_bootloader = atoi(argv[3]);
combine_file.check_type = atoi(argv[4]);
sprintf(combine_file.fn_bootloader,"%s",argv[5]);
sprintf(combine_file.fn_app_in,"%s",argv[6]);
sprintf(combine_file.fn_app_out,"%s",argv[7]);
sprintf(combine_file.fn_app_mask,"_upfw_app.bin",argv[7]);
sprintf(combine_file.fn_product,"%s",argv[8]);
get_debug_switch = atoi(argv[9]);
combine_file.size_app_max = atoi(argv[10]);
combine_file.flash_base = atoi(argv[11]);
sprintf(combine_file.fn_user,"%s",argv[12]);
}
3.3.2 文件读取和加工
FILE *p_bootloader_file_source=NULL;
FILE *p_app_file_source=NULL;
FILE *p_app_file_out=NULL;
FILE *p_app_file_out_mask=NULL;
FILE *p_product_file_out=NULL;
FILE *p_user_file_out=NULL;
p_bootloader_file_source = fopen(combine_file.fn_bootloader,"rb+");
if (!p_bootloader_file_source)
{
if (get_debug_switch > 0)
{
printf("read bootloader file error=%s\\r\\n",combine_file.fn_bootloader);
}
goto error;
}
p_app_file_source = fopen(combine_file.fn_app_in,"rb+");
if (!p_app_file_source)
{
if (get_debug_switch > 0)
{
printf("p_app_file_source error=%d\\r\\n",p_app_file_source);
}
goto error;
}
//读取bootloader文件
fseek(p_bootloader_file_source, 0, SEEK_END);
uint32_t get_bootloader_file_size = ftell(p_bootloader_file_source);
if (get_debug_switch > 0)
{
printf("bootloader file size=%d\\r\\n",get_bootloader_file_size);
}
if ((0 == get_bootloader_file_size) || (get_bootloader_file_size > combine_file.size_total))
{
if (get_debug_switch > 0)
{
printf("bootloader file size invalid=%d\\r\\n",get_bootloader_file_size);
}
goto error;
}
fseek(p_bootloader_file_source, 0L, SEEK_SET);
uint8_t *bootloader_data_buffer = (uint8_t *)malloc(sizeof(uint8_t)*get_bootloader_file_size);
if (!bootloader_data_buffer)
{
if (get_debug_switch > 0)
{
printf("bootloader buffer malloc error\\r\\n");
}
goto error;
}
fread(bootloader_data_buffer,1,get_bootloader_file_size,p_bootloader_file_source);
//读取APP来源文件
fseek(p_app_file_source, 0, SEEK_END);
uint32_t get_app_file_size = ftell(p_app_file_source);
if (get_debug_switch > 0)
{
printf("file size[app]=%d\\r\\n",get_app_file_size);
printf("file size[app + crc]=%d\\r\\n",get_app_file_size+4);
}
if ((0 == get_app_file_size) || (get_app_file_size > (combine_file.size_app_max-8)))
{
if (get_debug_switch > 0)
{
printf("app file size invalid\\r\\n");
}
goto error;
}
fseek(p_app_file_source, 0L, SEEK_SET);
uint8_t *app_data_buffer = (uint8_t *)malloc(sizeof(uint8_t)*get_app_file_size);
if (!app_data_buffer)
{
if (get_debug_switch > 0)
{
printf("app buffer malloc error\\r\\n");
}
goto error;
}
fread(app_data_buffer,1,get_app_file_size,p_app_file_source);
//把app内容进行校验生成带校验的app文件
uint32_t get_crc_value = crc32_customized(CONFIG_CRC32_POLY,CONFIG_CRC32_INIT_VALUE,CONFIG_CRC32_OUT_XOR,app_data_buffer,get_app_file_size);
if (get_debug_switch > 0)
{
printf("get_crc_value = %x\\r\\n",get_crc_value);
}
//缓存大小是 bootloader+sector+app
//新建一个bootloader大小+sector+app大小的缓存
//生产用的文件需要带CRC校验和字节大小,客户用的APP文件只包含CRC校验没有大小,大小在升级的时候自动加上去
//后续再优化APPSIZE和APPCRC的存储位置,节省FLASH空间
uint32_t size_product = combine_file.size_bootloader+combine_file.size_parameter+get_app_file_size;
uint8_t *product_data_buffer = (uint8_t *)malloc(sizeof(uint8_t)*(size_product));
if (get_debug_switch > 0)
{
printf("product file size = %d\\r\\n",size_product);
}
if (!product_data_buffer)
{
if (get_debug_switch > 0)
{
printf("bootloader buffer malloc error\\r\\n");
}
goto error;
}
//填充0xff
printf("fill 0xff\\n");
memset(product_data_buffer,0xff,size_product);
printf("write bootloader\\n");
//写入 bootloader
memcpy(product_data_buffer,bootloader_data_buffer,get_bootloader_file_size);
//加入 CRC和SIZE信息
memcpy(&product_data_buffer[combine_file.size_bootloader],&get_app_file_size,4);
printf("write app\\n");
//写入app
memcpy(&product_data_buffer[combine_file.size_bootloader+combine_file.size_parameter],app_data_buffer,get_app_file_size);
if (get_debug_switch > 0)
{
printf("app at=[%08x]\\r\\n",combine_file.flash_base+combine_file.size_bootloader+combine_file.size_parameter);
}
//输出 app_out
p_app_file_out = fopen(combine_file.fn_app_out,"wb+");
if (!p_app_file_out)
{
if (get_debug_switch > 0)
{
printf("write app crc file error\\r\\n");
}
goto error;
}
fwrite(app_data_buffer,1,get_app_file_size,p_app_file_out);
//输出 加密后的文件
uint8_t *app_mask_data_buffer = 0;
printf("combine_file.check_type=[%d]\\r\\n",combine_file.check_type);
p_app_file_out_mask = fopen(combine_file.fn_app_mask,"wb+");
if (!p_app_file_out_mask)
{
if (get_debug_switch > 0)
{
printf("write app mask file error\\r\\n");
}
goto error;
}
3.3.3 文件加密和压缩处理
if (T_CRYPT == combine_file.check_type)
{
int res = 0;
uint8_t *set_data_out = 0;
tinycrypt_enc_init();
uint8_t *buffer_out_temp = (uint8_t *) malloc(sizeof(uint8_t)*get_app_file_size);
if (!buffer_out_temp)
{
printf("buffer_out_temp malloc error \\r\\n");
}
uint32_t size_out = 0;
if (quicklz_compress_buffer(buffer_out_temp,app_data_buffer,get_app_file_size,&size_out) >= 0)
{
if (!size_out)
{
printf("size_out error \\r\\n");
goto error;
}
set_data_out = (uint8_t *) malloc(sizeof(uint8_t)*size_out);
if (!set_data_out)
{
printf("set_data_out malloc error \\r\\n");
res = -1;
goto error;
}
memset(buffer_data,0,COMPRESS_BUFFER_SIZE);
aes_en_buffer(set_data_out,buffer_out_temp,size_out);
fwrite(set_data_out,1,size_out,p_app_file_out_mask);
uint32_t aes_crc32 =crc32_customized(CONFIG_CRC32_POLY,CONFIG_CRC32_INIT_VALUE,CONFIG_CRC32_OUT_XOR,set_data_out,size_out);
if (get_debug_switch > 0)
{
printf("aes_crc32 mask = %x\\r\\n",aes_crc32);
}
fwrite(&aes_crc32,1,4,p_app_file_out_mask);
}
if (buffer_out_temp)
{
free(buffer_out_temp);
}
if (set_data_out)
{
free(set_data_out);
}
}
3.4 bootloader的设计
在启动后挂载文件系统,检查是否存在xxx.bin的文件,如果存在则检查文件是否合法,合法就进入解密和解压缩阶段,最后把文件的crc和size写入flash,复位跳转到app对应的地址上运行
int result = dfs_elm_mount(&g_fat,0,0);
PRINTF_APP_MAIN("result=%d\\r\\n",result);
//if (0 == check_boot(CONFIG_UBOOT_WAITING_TIME_S))
{
uint8_t get_firmware_name[32]={0};
if (check_firmware_file(get_firmware_name))
{
PRINTF_APP_MAIN("find the firmware file name=%s\\r\\n",get_firmware_name);
FRESULT res = f_open(&_update_file,get_firmware_name,FA_OPEN_EXISTING|FA_READ);
uint32_t get_crc_result = 0;
PRINTF_APP_MAIN("res=%d\\r\\n",res);
if (FR_OK == res)
{
uint32_t read_bytes = 0;
uint32_t total_bytes = _update_file.fsize-4;
uint32_t left_bytes = total_bytes;
uint32_t read_actual_bytes = 0;
uint32_t index_read = 0;
PRINTF_APP_MAIN("update_file.fsize=%d\\r\\n",_update_file.fsize);
PRINTF_APP_MAIN("total_bytes=%d\\r\\n",total_bytes);
/**/
if (total_bytes <= CONFIG_FLASH_APP_SIZE)
{
volatile uint32_t check_crypt_crc = 0;
hardware_crc32_short_start();
while (left_bytes > 0)
{
if (left_bytes > CONFIG_FLASH_SECTOR_SIZE)
{
read_bytes = CONFIG_FLASH_SECTOR_SIZE;
}
else
{
read_bytes = left_bytes;
}
FRESULT read_res = f_read(&_update_file,read_file,read_bytes,&read_actual_bytes);
if (0 == read_actual_bytes)
{
break;
}
check_crypt_crc = hardware_crc32_short(read_file,read_actual_bytes);
left_bytes = left_bytes - read_actual_bytes;
}
check_crypt_crc &= 0xFFFFFFFF;
(check_crypt_crc ^= 0x00000000);
PRINTF_APP_MAIN("check_crypt_crc=%x\\r\\n",check_crypt_crc);
FRESULT read_last_res = f_read(&_update_file,read_file,4,&read_actual_bytes);
if (FR_OK == read_last_res)
{
uint32_t source_crc = 0;
memcpy(&source_crc,read_file,4);
f_close(&_update_file);
memset(read_file,0,CONFIG_FLASH_SECTOR_SIZE);
PRINTF_APP_MAIN("source_crc=%x\\r\\n",source_crc);
if (check_crypt_crc == source_crc)
{
res = f_open(&_update_file,get_firmware_name,FA_READ);
if (FR_OK == res)
{
PRINTF_APP_MAIN("crypt_decode_part\\r\\n");
if (crypt_decode_part(&_update_file,CONFIG_FLASH_APP_ADDRESS,&get_crc_result,&total_bytes) >= 0)
{
f_close(&_update_file);
PRINTF_APP_MAIN("crypt_decode_part after\\r\\n");
PRINTF_APP_MAIN("total_bytes=%d\\r\\n",total_bytes);
PRINTF_APP_MAIN("get_crc_result=%x\\r\\n",get_crc_result);
uint8_t set_data_buffer[20];
set_data_buffer[0] = (uint8_t)(total_bytes);
set_data_buffer[1] = (uint8_t)(total_bytes>>8);
set_data_buffer[2] = (uint8_t)(total_bytes>>16);
set_data_buffer[3] = (uint8_t)(total_bytes>>24);
set_data_buffer[4] = (uint8_t)(get_crc_result);
set_data_buffer[5] = (uint8_t)(get_crc_result>>8);
set_data_buffer[6] = (uint8_t)(get_crc_result>>16);
set_data_buffer[7] = (uint8_t)(get_crc_result>>24);
memset(&set_data_buffer[8],0xff,12);
uint8_t idx_check=0;
PRINTF_APP_MAIN("app info[");
for (idx_check=0; idx_check<20; idx_check++)
{
PRINTF_APP_MAIN("%02x ",set_data_buffer[idx_check]);
if(idx_check==3)
{
PRINTF_APP_MAIN("| ");
}
}
PRINTF_APP_MAIN("]\\n");
g_flash_write_bytes(CONFIG_FLASH_APP_SIZE_ADDRESS,set_data_buffer,LENGTH_OF_ARRAY(set_data_buffer));
f_unlink(get_firmware_name);
hw_cpu_reset();
}
}
}
}
}
}
}
//else
{
int res_umount = dfs_elm_unmount(&g_fat);
PRINTF_APP_MAIN("res_umount=%d\\r\\n",res_umount);
PRINTF_APP_MAIN("without update file then check app format\\r\\n");
uint32_t get_app_size = flash_read_word(CONFIG_FLASH_APP_SIZE_ADDRESS);
uint32_t get_app_crc = flash_read_word(CONFIG_FLASH_APP_CRC_ADDRESS);
PRINTF_APP_MAIN("get_app_size=%d\\r\\n",get_app_size);
PRINTF_APP_MAIN("get_app_crc=%x\\r\\n",get_app_crc);
memset(read_file,0,CONFIG_FLASH_SECTOR_SIZE);
if ((get_app_size > 0) && (get_app_size<= CONFIG_FLASH_APP_SIZE))
{
uint32_t read_bytes,data_offset,calc_crc_val = 0;
uint32_t left_bytes = get_app_size;
uint32_t flash_addr_index = CONFIG_FLASH_APP_ADDRESS;
PRINTF_APP_MAIN("left_bytes=%d\\r\\n",left_bytes);
calc_crc_val = crc32_customized(CONFIG_CRC32_POLY,CONFIG_CRC32_INIT_VALUE,CONFIG_CRC32_OUT_XOR,CONFIG_FLASH_APP_ADDRESS,get_app_size);
PRINTF_APP_MAIN("calc_crc_val=%x\\r\\n",calc_crc_val);
if (get_app_crc == calc_crc_val)
{
PRINTF_APP_MAIN("run app\\r\\n");
iap_load_app(CONFIG_FLASH_APP_ADDRESS);
}
}
}
可以加个后门,比如上电后某个脚为某个电平停留在bootloader阶段,进行OTA响应。
3.5 在KEIL上的应用
3.5.1 准备资源
编写脚本文件另存为
firmware_update.bat
@echo off
set /a CONFIG_DEBUG_SWITCH=1
set /a CONFIG_FLASH_BASE=0x08000000
set /a CONFIG_FLASH_SECTOR_SIZE=(2*1024)
set /a CONFIG_FLASH_TOTAL_SIZE=(512*1024)
set /a CONFIG_BOOTLOADER_SIZE=(40*1024)
set /a CONFIG_APP_MAX_SIZE=(235*1024)
set /a CONFIG_CHECK_TYPE=3
set CONFIG_FN_BOOTLOADER=bootloader.bin
set CONFIG_FN_APP_SOURCE=app_.bin
set CONFIG_FN_APP_OUT=_upfw_.bin
set CONFIG_FN_PRODUCT=product_.bin
set CONFIG_FN_USER=sys.bin
::call clean.bat
copy %CONFIG_FN_BOOTLOADER% .\\bin
copy data_crypt.exe .\\bin
copy %CONFIG_FN_USER% .\\bin
cd bin/
data_crypt %CONFIG_FLASH_TOTAL_SIZE% %CONFIG_FLASH_SECTOR_SIZE% %CONFIG_BOOTLOADER_SIZE% %CONFIG_CHECK_TYPE% %CONFIG_FN_BOOTLOADER% %CONFIG_FN_APP_SOURCE% %CONFIG_FN_APP_OUT% %CONFIG_FN_PRODUCT% %CONFIG_DEBUG_SWITCH% %CONFIG_APP_MAX_SIZE% %CONFIG_FLASH_BASE% %CONFIG_FN_USER%
start %~dp0\\bin
3.5.2 Bootloader的KEIL配置
在keil的User 标签中添加AfterBuild/Rebuild脚本
$K\\ARM\\ARMCC\\bin\\fromelf.exe --bin --output=bin@L.bin ! L
将bootloader.bin存放到mdk项目文件目录下
3.5.3 APP的KEIL配置
3.5.4 编译后生成以下目标文件
upfw_app.bin是压缩和加密后的文件
app .bin是未加工的应用程序
product_.bin是用于量产时的烧录文件
4 应用场景
4.1 通过电脑升级固件
枚举成大容量存储设备,通过USB线连接到电脑后会枚举出一个U盘,把_upfw_app.bin复制到U盘根目录下后重启即可
可以参考这篇文章做一个USB大容量存储设备STM32大容量存储设备
4.2 通过U盘升级
工作在USB主机模式下,
把_upfw_app.bin复制到一个U盘根目录下,插入U盘后,主机识别到U盘根目录下的文件,拷贝到文件系统,复位重启即可
4.3 通过离线烧录器烧录
在量产时可以使用合并好的未加密的product_.bin
4.4 OTA升级
可通过ymodem协议把加密文件传输到文件系统根目录下,再重启即可,
可以参考涂鸦智能的串口协议的设计,可以参考rtthread自带的ymodem例子,也可以参考小米用到的xmodem来设计自己的OTA
5 总结
加入OTA后,分配给APP的FLASH大小可能会少很多,把运用在实际项目上的经验复盘到自己的开发板上,总结好理论经验,多做几次实际操作,按照这样的方法,应该可以内化吸收,把经验转换成我们内在的东西,再刻意地运用在新的项目上。
做任何事应该都有方法,赚钱有方法,工作有方法,帮助他人有方法,教育孩子有方法,努力是没有用的,有了正确的方法和方向的努力才有用。
全部0条评论
快来发表一下你的评论吧 !